All notes

Sep 2026 · 3 min read

Published

Build Log #9: The Batch That Got Stuck at Zero of Six

AuthorKervintz Noel
Filed underAI Engineering · Claude Code · Building in Public · Python
SeriesPart 9 of 11 · View the full case study

This one wasn't found by a test. I imported a handful of real statements, closed the app, came back later, and the History screen still showed one batch stuck at "Processing, 0 of 6" — with nothing in the UI that could act on it.

Root cause

A job can only be claimed by a worker while its status is QUEUED or RETRYING. If the backend process dies or restarts while a job is already claimed — status PROCESSING — nothing in the system was ever written to reclaim it. That job is orphaned permanently, the batch can never see every job reach a terminal state, and it sits at "Processing" forever.

The dev database confirmed it exactly: all six jobs on the stuck batch were PROCESSING, every attempt count still at 0, and the update timestamps staggered about 1.7 seconds apart — consistent with the backend restarting repeatedly during that session, each restart claiming the next queued job right before dying again.

Nothing in the design was wrong. I'd just never written down what happens when the process running the queue dies mid-job — until it actually did, on my own machine, with my own statements.

The fix

A reclaim function requeues anything stuck in PROCESSING, counting the reclaim itself as an attempt — so a job that keeps getting orphaned still eventually hits its retry limit instead of looping forever. It runs from two places: automatically on startup, before the worker starts (every PROCESSING row is guaranteed orphaned at that point, since nothing is running yet), and behind a manual retry endpoint that's more conservative — it only touches jobs stuck for at least 60 seconds, since a worker might legitimately still be working on one.

Eight new tests, and restarting the real backend reclaimed and fully reprocessed the actual batch that had been stuck — and a couple of others that had quietly orphaned the same way without anyone noticing.


Next

Back to the plan: Dashboard, Review, and the transaction drawer.

I write these as I go. You can follow along here or on Hashnode, where I'll start cross-posting.

All notes