[BUG] Killing a Workflow leaks its in-flight agent runs, permanently blocking archive_session

Status Open
Reported on v2.1.146
Maintainer reply None cached
Activity 0 comments · opened Sep 10, 2026

What's wrong

Stopping a Workflow while its agents are in flight leaves those agent runs registered as started with no terminal record. Nothing reaps them. The session then reports "live work" for the rest of its life, and mcp__ccd_session_mgmt__archive_session refuses every call until the session ends:

Session self ("<title>") was not archived: it still has live work
(an agent run, a Remote Control client, a queued message or a background task).
Wait or ask the user; they can also archive it from the sidebar.

No process is alive. It is a stale registration, so the refusal is factually wrong about the session and correct about its own bookkeeping. Archiving from the sidebar still works, which is the only recovery I found.

This is the third report of one class, and the first two were closed by the stale bot with no maintainer response:

| | trigger | symptom |
| --- | --- | --- |
| #60095 (closed stale) | subagent exits with background Bash tasks | chips pinned to "Running", Stop no-ops |
| #79178 (closed stale) | /model switch orphans a Monitor task | spinner never clears, TaskStop says "No task found" |
| this | TaskStop on a Workflow with agents mid-run | archive_session refuses permanently |

The consequence has escalated. The earlier two leave a cosmetic indicator that lies. This one hard-blocks a lifecycle operation: the session can never be archived by the tool it is told to use, and the message it gets back points at causes that are not the cause.

What should happen

Either would fix it:

  1. Aborting a workflow closes out its in-flight agents in the journal, as failed or cancelled. The workflow record already knows it was killed (status: "killed", error: "Error: Workflow aborted"); its agents do not.
  2. The live-work check ignores agent runs belonging to a workflow in a terminal state. Cheaper, and it also covers whatever else can orphan a run.

A reaper for registrations with no live process behind them would cover all three issues in the table.

Steps to reproduce

  1. Start a Workflow with enough agents that several run concurrently.
  2. Call TaskStop on its task id while agents are mid-run.
  3. Call archive_session with session_id: "self".

It refuses, and keeps refusing. Calling TaskStop again answers No task found with ID: <id>, which reads as terminal and is not: the task record is gone, which is what a kill does, and it says nothing about the agents that task spawned.

Measured

One session in a private repo, 2026-09-10, all times UTC. Two workflows ran, which gives a control in the same session. The second was aborted at 04:53:03.247Z:

status = killed
error  = Error: Workflow aborted
agentCount = 93

Journal counts from ~/.claude/projects/<project>/<session>/subagents/workflows/<runId>/journal.jsonl:

| workflow | started | result | failed | outstanding |
| --- | --- | --- | --- | --- |
| completed | 56 | 50 | 6 | 0 |
| killed | 95 | 87 | 4 | 4 |

The four orphaned agents each stopped writing between 04:51:54Z and 04:53:03Z, at the abort. Nothing in that directory was written afterwards, and ps showed no agent processes. So from the first archive attempt onward there was demonstrably nothing running.

Five archive_session calls, all with the literal "self", spread over 43 minutes, all refused with the identical message: 05:00:45, 05:01:10, 05:31:55, 05:40:43, 05:43:23. Archiving from the sidebar succeeded at about 05:43:57.

Counting the journal is a reliable diagnostic, and as far as I can tell it is the only one available from inside the session:

python3 -c "
import json,io,collections,sys
c=collections.Counter(json.loads(l).get('type') for l in io.open(sys.argv[1]) if l.strip())
print('outstanding =', c['started']-c['result']-c['failed'])" <journal.jsonl>

Why it costs more than the refusals

The session in question did everything right and still reported the wrong cause to its user, twice, in writing. It checked its task list, ps, and the ports; found nothing; and was left with the two causes in the message that cannot be inspected from inside a session (a Remote Control client, a queued message), so it concluded the block was on the user's side and asked them to clear it. There was nothing to clear.

It had even counted the journal and printed outstanding = 4, seven minutes before its second refusal, and discarded the number because TaskStop had contradicted it.

I have filed the message half separately as #93269, since a message that named the cause would have made this diagnosable even unfixed. This issue is the leak itself, which is the part that makes the state unrecoverable.

Environment

  • Claude Desktop 1.49585.0 (macOS 26.6.2, build 25G83, arm64)
  • Claude Code CLI 2.1.146. Not the latest (2.1.267 at the time of filing); flagging that rather than ticking the box, since I have not retested on it.
  • Platform: Anthropic API

Related

  • #93269: the refusal message names four causes and identifies none. Same incident, different half.
  • #79178 and #60095: the same class (work torn down with no terminal record, leaving a registry entry nothing can clear), both closed stale.
  • #93259: the other branch of the same refusal message builder.

View original on GitHub ↗