I Audited My AI's To-Do List. A Quarter of It Was Already Done.
My coding agent has a to-do list. It lives in a public GitHub repo — one issue per task, labelled by project, opened and closed automatically as the agent works. At the start of every session the still-open issues get read back in, so a backlog survives across sessions and across machines. The agent picks up exactly where the last instance left off. This morning I asked it to reconcile that backlog against reality. Eleven open tasks for the current project. Three of them were already done — one finished five days ago, two finished the same afternoon they were created. They'd been sitting "open" ever since, getting faithfully restored into session after session as live work. That's a 27% ghost rate on a single morning's sample. Small numbers, but the reason they were ghosts isn't a fluke. It's baked into how agents report what they've done. The system: a to-do list that lives in the open First, the setup, because the bug only makes sense once you see the machinery. When the agent creates a task mid-session, a hook fires and turns it into a GitHub issue, tagged with a project: label. When the agent marks a task complete, another hook closes the matching issue. Each task carries a stable id — a marker embedded in the issue body — so the whole thing is idempotent: restoring a task and re-creating it finds the existing issue instead of minting a duplicate. The payoff is that the agent's memory of what it's supposed to be doing is no longer trapped in a context window. It's durable, inspectable, and — because it's a normal GitHub repo — something I can read, edit, or hand to another tool. Working in the open, for the agent's own task state. At session start, the loop runs in reverse: query every open issue with this project's label, re-create each as a live task. The backlog comes home. The leak Here's the asymmetry that produces ghosts. The create signal is automatic. The agent cannot start working on a task without first creating it — the act of tracking the work is coupled to the act of doing the work. The hook fires every single time, because the tool call that triggers it is one the agent always makes. The complete signal is not automatic. It's a separate, optional act of bookkeeping that has to happen after the work is already done — at exactly the moment the agent's attention is moving on to the next thing, or the session is ending, or the human said "great, thanks" and the conversation wrapped. Closing the issue is the one step with nothing downstream depending on it. So it's the step that gets skipped. The result: creates are reliable, completes are lossy. Open issues accumulate not because the work isn't done, but because finishing and recording that you finished are two different actions, and only the first one is load-bearing. Signal Coupled to Fires reliably? Task created Starting the work (mandatory) Yes Task completed Bookkeeping after the work (optional) No Every restore then re-presents the un-closed ghosts as if they were live. The next session dutifully picks them up, re-verifies work that shipped weeks ago, and — if nobody audits — does it again next time. A write-biased loop doesn't just lose information; it actively re-surfaces stale information as current. What the audit actually looked like The reconciliation itself is the interesting part, because the fix for "the agent's self-report is unreliable" is don't trust the self-report — check reality. Each of the three ghosts was closed only on positive, external evidence: A task to email someone a link → confirmed by an actual sent thread in Gmail with that recipient and a matching subject line. A task to schedule a call → confirmed by the live calendar event existing, with the attendee invited and the invite showing needsAction (sent, awaiting reply — the difference between "I called the API" and "the thing actually happened"). A security task to stop tracking a secrets directory in a backup repo → confirmed by reading the repo's git tree directly: zero matching paths, plus the directory now in .gitignore. Both halves of the fix, verified against the deployed artifact rather than the agent's memory of having done it. In every case the evidence lived in a system other than the task tracker — the email server, the calendar, the git tree. The task list said "open." Reality said "done." When those disagree, reality wins, and the only way to know reality disagrees is to go and look. Notice what each check has in common: it queries the downstream observable, not the handoff. "I sent the email" is a claim; a thread in the Sent folder is a fact. "I closed the issue" is a claim; an empty git tree is a fact. The audit is built entirely out of facts, which is why it can correct claims. The general shape This isn't really about task lists. It's about any system where an agent reports its own completion. A deploy pipeline that marks itself "shipped" when the merge lands, not when the artifact is observably live. A migration script that logs "done" per row without reading the row back. A test suite reported green by the thing that wrote the tests. In every case there's a handoff signal that's cheap and automatic, and an effect signal that's expensive and easy to skip — and the gap between them fills up with confident, wrong status. There are only two honest fixes: Make the completion signal as automatic as the creation signal — couple "record that it's done" to something the agent can't skip, the way creation is coupled to starting. Hard, because completion genuinely is a separate event from doing the work. Stop trusting self-reported completion and verify against reality — periodically reconcile the claimed state against the downstream observable, and let reality close the loop. The audit is option 2: a garbage collector for ghost state. It works, and I'll keep running it. But it's a patch on the real disease, which is the write-bias itself. The audit cleans up after the leak; it doesn't stop the leak. The One-Sentence Version Agents announce intent reliably and completion unreliably — because starting a task is coupled to doing the work while finishing one is just bookkeeping — so any system that trusts an agent's own "done" will silently fill with already-finished work, and the only cure is to check reality instead of the report. Nick Meinhold builds AI-powered tools at enspyr.co. The task tracker described here is driven entirely by Claude Code session hooks; the audit in this post was run by Claude, against its own backlog, and found its own ghosts.
This is a summary aggregated from Dev.to. Read the complete article on the original site:
Read full article at Dev.to