[BUG] Workflow resume leaves orphan 'started' journal markers, inflating /workflows pending counts
What's Wrong?
When a background Workflow run is stopped (via TaskStop) while agents are in flight, their started entries in the run's journal.jsonl are left without matching result entries. Resuming the run with resumeFromRunId re-runs those incomplete keys under new agentIds and appends new started entries, but the orphan started markers from the killed run are never reconciled or pruned.
The /workflows phase progress appears to count started-without-result per agentId, so after a resume the pending count is inflated by zombie markers from the killed run. In a real run we observed a phase showing "3 pending" while only 1 agent was actually live; the other 2 were orphan markers from before the kill. Users misread the phase as stuck.
Execution correctness is unaffected — the resumed run completes normally and cached results replay correctly. The problem is display/bookkeeping only.
What Should Happen?
On resume, started markers from a previous run that have no matching completion record should be excluded from pending counts (or surfaced explicitly as stale/zombie), so /workflows reflects only agents that are actually live in the current run.
Steps to Reproduce
- Launch a minimal dynamic workflow with one fast agent and two slow agents:
``js``
export const meta = {
name: 'repro-orphan-markers',
description: 'Repro: kill/resume leaves orphan started markers in journal',
phases: [{ title: 'Probe' }],
}
phase('Probe')
const results = await parallel([
() => agent('Do not use any tools. Return exactly the word: fast-done', {label: 'fast'}),
() => agent("Run this exact Bash command and wait for it to finish: python3 -c 'import time; time.sleep(120)'. After it completes, return exactly the word: slow1-done", {label: 'slow-1'}),
() => agent("Run this exact Bash command and wait for it to finish: python3 -c 'import time; time.sleep(120)'. After it completes, return exactly the word: slow2-done", {label: 'slow-2'}),
])
return results
- Wait ~30s so the fast agent completes and the slow agents are mid-flight, then stop the workflow task (TaskStop).
journal.jsonlnow contains 3startedentries and only 1result— 2 orphans:
````
{"type":"started","key":"v2:c386ee90...","agentId":"a9c248058eb16adcf"}
{"type":"started","key":"v2:8acf0349...","agentId":"a8e4702ddd866ef95"}
{"type":"started","key":"v2:ccc402d7...","agentId":"aa36c11cebbc13160"}
{"type":"result","key":"v2:8acf0349...","agentId":"a8e4702ddd866ef95","result":"fast-done"}
- Resume with
Workflow({scriptPath, resumeFromRunId}). The two incomplete keys re-run under new agentIds; the orphan markers stay. Mid-flight journal:
````
... (4 lines above unchanged) ...
{"type":"started","key":"v2:c386ee90...","agentId":"a326f4c5e07f33303"}
{"type":"started","key":"v2:ccc402d7...","agentId":"a5a3b2cfd728aaafc"}
At this point started-without-result counts 4 agents pending while only 2 are live. This matches the inflated pending count observed in /workflows.
- Let the run finish. The new agentIds get
resultentries; the orphan markers (a9c248058eb16adcf,aa36c11cebbc13160) permanently remain without completion records. Any agentId-level accounting stays polluted; key-level accounting recovers.
Claude Code Version
2.1.207 (Claude Code)
Is this a regression?
I don't know (first observed on 2.1.19x, still reproduces on 2.1.207).
Platform
Anthropic API
Operating System
macOS (Darwin 25.5.0)
Terminal/Shell
Terminal.app (macOS)
Additional Information
- Originally observed during a long multi-phase workflow: after kill + resume,
/workflowsshowed the current phase as "3 pending" with only 1 agent live, which made the phase look hung. - Workaround: treat pending counts right after a resume as possibly including zombies from the previous run, and judge progress by live agents; stopping and resuming again resets the display.