Verbatim symptom
the todo stays open forever# The to-do that never closed
I asked the assistant to get Claude to do something, Claude did it, and a day later the same item was still sitting on my list, open, looking me in the eye. That's the whole bug, really. Everything below is just how the thing actually works.
Symptom
The owner says something like "get Claude to do X" to the voice assistant. Two things get written. An escalation-queue entry goes in, which is what Claude picks up. And then, seconds later in that same turn, the capture path mints a to-do for the same X.
Claude ships the work. The escalation gets marked done. The to-do? The to-do stays open forever.
That's the exact string I kept coming back to, because it's so flat and so true. Nothing errors. Nothing logs a warning. The list just quietly grows, and the owner, seeing an open item for work that's already delivered, asks for it again. So Claude does it again. Fun.
On 2026-08-06 I measured it properly. Of 31 escalations measured, 3 were live phantoms, sitting among 66 open to-do, blocker and question thoughts. One pair was 2.85 seconds apart: the escalation and its twin to-do, written in the same breath, one of them destined to be closed and the other to hang around like a coat nobody claims.
Three out of 66 doesn't sound dramatic. But every open-item count was inflated by that much, and the count is the thing the owner trusts when deciding what to ask for next.
What we thought it was
My first guess was the boring one: Claude wasn't marking things done properly. Maybe the completion step only touched the escalation and skipped some cleanup I assumed existed. That was believable because the escalation did close, so something was running at the end, and it's easy to assume that "something" also swept up related records.
Second guess, and I held onto this one longer than I should have: the owner was just double-capturing. Say it once to the assistant, say it again later when the item looks unresolved, and you'd see duplicates without any bug at all. People repeat themselves. I repeat myself (anyway, you've noticed).
Both theories had the same flaw, which is that they explained the symptom without explaining the 2.85 seconds. A human re-asking doesn't happen within three seconds in the same turn. That gap was the clue I'd been ignoring. Two writes that close together aren't two decisions; they're one decision taking two routes.
Root cause
Two paths both react to the same utterance. The escalation path sees "get Claude to do X" and writes a queue entry. The capture path, which exists to catch actionable things the owner says, also sees X and mints a to-do. Neither knows the other fired.
The point where this lives is src/conversationSession.js:348@e5c2b2f, in the repository claude-voice-convo. That's where the escalation gets written, and crucially it's where nothing ties the escalation to the thought the capture path creates a moment later. The to-do is born with no reference to the escalation that spawned it. It has text, it has a type, and it has no parent.
So when Claude finishes and the escalation is marked done, there's genuinely no link to follow. The to-do is an orphan from birth. Closing the escalation can't resolve it, because the system has no way to know they're the same item. The only thing connecting them is that they say roughly the same words, and similar words are not an identity.
The fix
The fix stamps the escalation id on the thought at write time. When the escalation is created, its id goes onto the to-do that the capture path mints, so the relationship is recorded the instant both exist. After that, closing an escalation resolves its to-do exactly. No guessing, no matching.
This went in as commit e5c2b2f on 2026-08-06, the same day I detected it.
Here's the part I want to be clear about, because it's where I almost went wrong. There's an obvious tempting extra: for the old phantoms, and for any case without a stamp, just find to-dos whose text looks like the closed escalation and resolve those too. I didn't do that. Text-similar candidates are reported but never auto-resolved.
The reason is simple. A false link would bury a real open item. If two things merely sound alike and I close one because the other finished, I've silently deleted work somebody still needs. A phantom that stays visible costs a little noise. A real item that vanishes costs the thing the whole list exists to prevent. So similar text gets surfaced for a human to look at, and only an exact id closes anything.
Why it happened
It survived review because each path was correct on its own. The escalation path did its job. The capture path did its job. Read either in isolation and you'd approve it, and I'm fairly sure that's what happened.
The bug lived in the gap between two reasonable pieces of code, and review tends to look at diffs, not at what two unrelated diffs do when the same sentence triggers both. Nobody was reading the interaction. There was also no test that said "one utterance produces exactly one open item", which is the invariant that actually mattered.
And the failure was silent. Nothing broke loudly, so nothing demanded attention. It only showed up as a slowly drifting count, which is the sort of thing you notice by feel before you notice it by evidence.
How to avoid it
If two code paths can both react to one input, decide up front which one owns the resulting record, and make the other one attach to it rather than create its own. Duplicate creation is the root; cleanup is only the symptom's treatment.
Give related records a real identifier at write time. Linking after the fact by comparing text will always be a guess, and guesses that auto-resolve are dangerous in proportion to what they can delete.
Write the invariant as a test: one input, one open item. And measure the thing you're counting. The 2.85 seconds only mattered because I finally looked at timestamps instead of theories.
Mostly, be suspicious when something closes and its shadow doesn't. The to-do stays open forever, and that's usually a link nobody wrote.
Every commit, file reference and date above is checked
against this incident's Noctem evidence pack before publication. Evidence:
escalation-queue-phantom-todos-never-closed.