Eight Tasks Went Out. The Report Came Back With Nine Outcomes

The agent finished the work. The report about the work was the part that broke, and once the numbers stop adding up you can't tell a normal pause from a real cascade of failures.

The tally read five done, three failed, one blocked. Eight tasks had gone out that morning.

I added the numbers twice before I believed the second total. Five and three and one is nine. Eight tasks had produced nine outcomes, and that's the kind of arithmetic that makes you stop trusting a spreadsheet before you've even opened it.

So I stopped trusting the run.


Nothing about it looked broken, once I actually checked. The three "failed" tasks had real diffs sitting in the working tree. Tests green. Nothing obviously wrong with any of them. The agent hadn't lied to me. It had genuinely finished the work. What had broken wasn't the code. It was the vocabulary the run used to describe itself, and once that breaks, every number downstream of it is a guess wearing a report's clothes.

Then the specific panic starts, the kind that comes from not knowing which direction to look. A code bug points at a stack trace. A trust problem points at everything at once. I opened the logs for all eight tasks, one at a time, because the summary that was supposed to save me that hour had stopped being something I could act on.


Here's what the slow version told me.

One of the "failed" tasks hadn't failed at all. It had stopped on purpose, mid-run, to wait on something a later task was supposed to hand it. That's a completely ordinary thing for a run to do. Except somewhere in the reporting, that deliberate pause got written down twice: once as a failure, because the task didn't finish, and once as blocked, because it was, correctly, blocked. One event, two ledgers, and neither ledger knew the other existed.

That double-booked line matters less on its own than for what it does to everything after it. Three other tasks were downstream of the one that paused. Each of those got logged as an independent failure instead of "waiting on a dependency that hasn't resolved," and a resumable pause dressed itself up as a cascade of real breakage. You can't tell those two situations apart from a status line. You can only tell them apart by reading, which is exactly the hour a status line exists to save you.

A code bug is something you go fix. A report you've stopped trusting is something you stop reading, and the moment you stop reading it, the whole reason it existed is gone.


I've seen the other ways this shows up too, on other runs, other weeks.

A summary reports a task complete, and the diff is sitting there uncommitted. Worse: staged, but never committed, because staged looks like progress on a status check and nobody double-checks progress. The next time I commit anything by hand in that repo, those orphaned changes ride along under a message that has nothing to do with them. The claim said done. The working tree said otherwise. Nobody catches the gap for days, because why would you go checking a task the run already told you was fine.

Or a task stops mid-work with real changes sitting uncommitted, and the next task's own commit sweeps the whole tree along with it, including files it never touched. Two tasks' worth of change now lives under one task's name, and separating them later means picking a commit apart by hand, deciding hunk by hunk which task actually owns which line. Nobody ever told the working tree it was only allowed to commit what it meant to commit, and an agent has no way to guess that boundary on its own.

And when a task genuinely fails, the instinct is to tidy up after it. Delete the branch, discard the working state, leave a clean slate for the next attempt. The one artifact you'd actually want, when a task fails, is whatever it built before it broke, and tidying up is exactly what throws that away first.


What I want from a run isn't perfection. Agents fail. Tasks pause partway through and wait on something else. That's the job we signed up for, and pretending otherwise is its own kind of dishonesty. What I want is for the report to describe the actual shape of what happened instead of rounding toward whatever's convenient for the summary.

Which is the fix, as far as I've watched it play out since: treat every hand-off between tasks as a transaction instead of a vibe. The working tree has to be clean at the boundary, before the run starts, between every task, at the very end. Whatever a task built either gets committed under that task's name, gets set aside somewhere you can go find it later, or the run stops cold and says exactly that instead of quietly rolling forward on top of a mess. Nothing carries into the next task silently. A skipped step gets named as skipped, never folded into a green checkmark.

And the vocabulary gets fixed at the root. Paused-and-waiting counts as its own outcome now, distinct from failed. Four statuses, one dispatch, and the count of tasks going in matches the count of outcomes coming out. Boring, arithmetically boring, which is exactly what I want from a line I'm about to trust with my morning.

Preserved work stays preserved. Nothing sweeps it away on its own once it's set aside, and I went and checked that specifically, because I've been burned before by tools that say "saved" and mean "saved until the next cleanup pass."


The next run I kicked off after that came back eight for eight. Five done, two failed for reasons I could read in under a minute each, one blocked on something upstream and labeled as exactly that, nothing else.

Eight tasks. Eight outcomes. I didn't have to add it twice.