TL;DR — what's broken for real users
Multi-agent orchestrator pipelines on claude-cli lose parent context after every subagent completion. The orchestrator forgets it spawned the child mid-pipeline, mis-tracks pipeline_status, and asks the user "wasn't that done already?" — i.e. the parent agent becomes operationally amnesic.
This is not theoretical. It is the dominant failure mode when running a planner/coder/checker pipeline over more than 2-3 turns. The class has been open since at least 2026-04 (#69118) with 5+ distinct triggers reported by separate users in that thread. Two upgrades (2026.6 live-session rewrite, 2026.6.5) changed the surface but not the class. I'm filing this as a fresh issue because the canonical thread has lost signal — automation stripped P1, the last 5 maintainer-visible signals are "still reproducing" with no acknowledgment, and the new live-session path warrants a clean root-cause writeup.
Environment
Concrete repro (deterministic, full pipeline)
- Orchestrator main session, holding a multi-turn conversation establishing a project, plan, and pipeline state.
- Spawn a planner subagent with
sessions_spawn({ agentId: "planner", mode: "run", thinking: "high" }). Planner runs ~5-15 min, writes to project DB, returns a completion message.
- Orchestrator receives the completion message and continues the conversation.
Expected: orchestrator references the planner's output, knows it spawned the planner, advances pipeline_status accordingly.
Actual: orchestrator behaves as if the conversation just started. It re-asks the user what step we're on, re-checks DB state it should already remember, and frequently fails to "see" the completion event at all — the user has to say "isn't task X done?" before the orchestrator queries the DB and discovers the work was completed minutes ago. This repeats every spawn → completion cycle.
In a single 6-hour pipeline session I just ran, this happened at every single child completion (12+ cycles), and several times the orchestrator missed the completion event entirely on its own; the user explicitly had to ask "is wave X done?" before the parent re-discovered state from the DB.
The on-disk evidence: my orchestrator session jsonl is currently 100 KB / 81 lines for a 6-hour, ~30-tool-call conversation. The line count is suspicious for the wall-time and operation density — consistent with aggressive prefix discarding at every subagent completion event.
Root cause on 2026.6.5 (verified at dist source)
Two layers, both still unaddressed in the released dist:
Layer 1 — announce caller drops extraSystemPromptStatic
subagent-announce-delivery-D8I4cA_R.js (line lookups via the released dist) constructs directAgentParams for the in-process dispatchGatewayMethodInProcess("agent", …) call without extraSystemPromptStatic or extraSystemPrompt. Grep on the released dist:
$ grep -c "extraSystemPromptStatic" dist/subagent-announce-delivery-*.js
0
When the gateway agent handler forwards this, prepare sees extraSystemPromptStatic === undefined && extraSystemPrompt === "" and computes extraSystemPromptHash over empty content. Same orchestrator session, but the static prompt the binding was originally cached against (e.g., "You are in a WebChat direct conversation. ... NO_REPLY ...", ~219 chars) is absent on the announce-driven turn.
Layer 2 — comparator treats "absent" identically to "now empty"
2026.6 moved the per-turn comparator into the live-session fingerprint: claude-live-session-CSrEm4Vm.js → buildClaudeLiveFingerprint(…) hashes systemPromptHash, extraSystemPromptHash, promptToolNamesHash, mcpConfigHash, skillsFingerprint, normalized env values, and argv. On mismatch, runClaudeLiveSessionTurn calls closeLiveSession(session, "restart") and spawns a fresh claude --resume <id>.
The released dist hashes 4 prompt-derived inputs (verified by grep: extraSystemPromptHash, promptToolNamesHash, skillsFingerprint, systemPromptHash all referenced inside buildClaudeLiveFingerprint).
The comparator does not distinguish "the caller did not assert a static prompt this turn" from "the static prompt is now legitimately empty." Layer 1 produces the first case; the comparator interprets it as the second and forks/restarts.
Net effect on 2026.6.5
- Session binding survives (resume id stays stable, so
claude --resume <id> does retrieve prior turns from disk).
- The live process is recycled on every turn that comes through the announce path. Persistent-process / stream-input optimization never engages for orchestrator runs that include any subagent.
- Conversation effective recency is lost — the resume mechanism delivers history via
--resume <id> but the running orchestrator's working memory of the current turn (the in-flight tool call sequence, the freshly spawned children, the recent completion events) is interrupted by the process restart. The orchestrator behaves as if just resumed cold — repeatedly.
z1pp090's 2026-06-11 comment in #69118 documents the close/start pair firing back-to-back on turns ~2 s apart, ruling out idle-driven restart. My session shows the same signature.
Why the canonical thread isn't moving
Thread #69118 has at least 5 distinct triggers reported (groupIntro drift, subagent-announce delivery, dmScope:"main" channel switching, single-channel Telegram per-channel-peer, 2026.6 live-session fingerprint mismatch). ClawSweeper auto-stripped the original P1 label and added clawsweeper:needs-info based on a failed Codex review run, without engagement with the actual content. The last 3 substantive comments are users confirming "still reproducing on the next version" with no maintainer reply. The original comparator-tolerance proposal was never implemented; reports have been moved into this issue from at least 3 other issues (#81460, #83250, others), so the thread has become a duplicate sink with no progress.
This is not a complaint about the bot — it's a request for the issue to be re-scoped at the class level so a fix can land.
Proposed fix (two-part, minimal surgery)
Fix A — comparator symmetry (Layer 2, was Patch E in our local repro)
Make buildClaudeLiveFingerprint's prompt-derived hash comparison treat "absent / empty" as "no assertion, do not invalidate." One-line semantic change:
// pseudocode against the equivalent comparator point inside buildClaudeLiveFingerprint
- if (binding.extraSystemPromptHash !== currentExtraSystemPromptHash)
- return { invalidatedReason: "system-prompt" };
+ const storedHash = normalizeOptionalString(binding?.extraSystemPromptHash);
+ const currentHash = normalizeOptionalString(currentExtraSystemPromptHash);
+ if (storedHash && currentHash && storedHash !== currentHash)
+ return { invalidatedReason: "system-prompt" };
Same pattern applies to promptToolNamesHash if it can legitimately be unset on a turn.
Rationale (also from the canonical thread): the hash field's semantic intent is "did the static system context change?" undefined/empty means "caller has nothing to assert this turn," not "the static context is now empty." The current code conflates these and falsely invalidates.
We applied Fix A locally on 2026.5.3 in May (single-line dist edit), Boss-verified the orchestrator retained context across announce within 30 minutes of patching. The patch was wiped by the 2026.6 upgrade. We re-verify it works each upgrade and re-apply.
Fix B — caller-side, thread static through announce (Layer 1)
subagent-announce-delivery-*.ts should populate extraSystemPromptStatic from session-bound directChatContext (or pass a sentinel that tells the gateway agent handler to compute the same static parts it would for a normal inbound turn).
This is the architecturally correct fix. Without it, Fix A papers over the symptom; with it, the comparator never sees the mismatch in the first place because the announce turn carries the same static prompt content as a user-typed turn.
Both fixes are non-breaking. The comparator change is the safer immediate landing; Fix B is the durable solution.
Acceptance criteria
- Orchestrator on
claude-cli running a multi-agent pipeline retains turn-local working memory across subagent completion events (no process restart, no rollback to resume-cold).
cli exec log shows reuse=reusable on announce-driven turns when binding is otherwise valid; no cli session reset: provider=claude-cli reason=system-prompt on every spawn cycle.
- Live process count stays stable across N consecutive turns with subagent spawns (currently one
close … reason=restart per turn).
- No regression on the legitimate-invalidation case: changing the bound channel's static directChatContext (e.g. moving to a different provider's DM) does invalidate.
Relationship to existing reports
I'm happy to attach instrumented [cli-prompt-trace] output and the dist file/line numbers for the relevant function bodies if maintainers want them — say the word.
TL;DR — what's broken for real users
Multi-agent orchestrator pipelines on
claude-clilose parent context after every subagent completion. The orchestrator forgets it spawned the child mid-pipeline, mis-trackspipeline_status, and asks the user "wasn't that done already?" — i.e. the parent agent becomes operationally amnesic.This is not theoretical. It is the dominant failure mode when running a planner/coder/checker pipeline over more than 2-3 turns. The class has been open since at least 2026-04 (#69118) with 5+ distinct triggers reported by separate users in that thread. Two upgrades (2026.6 live-session rewrite, 2026.6.5) changed the surface but not the class. I'm filing this as a fresh issue because the canonical thread has lost signal — automation stripped
P1, the last 5 maintainer-visible signals are "still reproducing" with no acknowledgment, and the new live-session path warrants a clean root-cause writeup.Environment
2026.6.5 (5181e4f), Linux arm64, loopback gatewayanthropic/claude-fable-5andanthropic/claude-opus-4-7viaclaude-clibackend (auth profileanthropic:claude-cli, OAuth)sessions_spawnfrom the orchestrator main sessionConcrete repro (deterministic, full pipeline)
sessions_spawn({ agentId: "planner", mode: "run", thinking: "high" }). Planner runs ~5-15 min, writes to project DB, returns a completion message.Expected: orchestrator references the planner's output, knows it spawned the planner, advances pipeline_status accordingly.
Actual: orchestrator behaves as if the conversation just started. It re-asks the user what step we're on, re-checks DB state it should already remember, and frequently fails to "see" the completion event at all — the user has to say "isn't task X done?" before the orchestrator queries the DB and discovers the work was completed minutes ago. This repeats every spawn → completion cycle.
In a single 6-hour pipeline session I just ran, this happened at every single child completion (12+ cycles), and several times the orchestrator missed the completion event entirely on its own; the user explicitly had to ask "is wave X done?" before the parent re-discovered state from the DB.
The on-disk evidence: my orchestrator session jsonl is currently 100 KB / 81 lines for a 6-hour, ~30-tool-call conversation. The line count is suspicious for the wall-time and operation density — consistent with aggressive prefix discarding at every subagent completion event.
Root cause on 2026.6.5 (verified at dist source)
Two layers, both still unaddressed in the released dist:
Layer 1 — announce caller drops
extraSystemPromptStaticsubagent-announce-delivery-D8I4cA_R.js(line lookups via the released dist) constructsdirectAgentParamsfor the in-processdispatchGatewayMethodInProcess("agent", …)call withoutextraSystemPromptStaticorextraSystemPrompt. Grep on the released dist:When the gateway agent handler forwards this,
prepareseesextraSystemPromptStatic === undefined && extraSystemPrompt === ""and computesextraSystemPromptHashover empty content. Same orchestrator session, but the static prompt the binding was originally cached against (e.g.,"You are in a WebChat direct conversation. ... NO_REPLY ...", ~219 chars) is absent on the announce-driven turn.Layer 2 — comparator treats "absent" identically to "now empty"
2026.6 moved the per-turn comparator into the live-session fingerprint:
claude-live-session-CSrEm4Vm.js→buildClaudeLiveFingerprint(…)hashessystemPromptHash,extraSystemPromptHash,promptToolNamesHash,mcpConfigHash,skillsFingerprint, normalized env values, and argv. On mismatch,runClaudeLiveSessionTurncallscloseLiveSession(session, "restart")and spawns a freshclaude --resume <id>.The released dist hashes 4 prompt-derived inputs (verified by grep:
extraSystemPromptHash,promptToolNamesHash,skillsFingerprint,systemPromptHashall referenced insidebuildClaudeLiveFingerprint).The comparator does not distinguish "the caller did not assert a static prompt this turn" from "the static prompt is now legitimately empty." Layer 1 produces the first case; the comparator interprets it as the second and forks/restarts.
Net effect on 2026.6.5
claude --resume <id>does retrieve prior turns from disk).--resume <id>but the running orchestrator's working memory of the current turn (the in-flight tool call sequence, the freshly spawned children, the recent completion events) is interrupted by the process restart. The orchestrator behaves as if just resumed cold — repeatedly.z1pp090's 2026-06-11 comment in #69118 documents the close/start pair firing back-to-back on turns ~2 s apart, ruling out idle-driven restart. My session shows the same signature.
Why the canonical thread isn't moving
Thread #69118 has at least 5 distinct triggers reported (groupIntro drift, subagent-announce delivery,
dmScope:"main"channel switching, single-channel Telegramper-channel-peer, 2026.6 live-session fingerprint mismatch). ClawSweeper auto-stripped the originalP1label and addedclawsweeper:needs-infobased on a failed Codex review run, without engagement with the actual content. The last 3 substantive comments are users confirming "still reproducing on the next version" with no maintainer reply. The original comparator-tolerance proposal was never implemented; reports have been moved into this issue from at least 3 other issues (#81460, #83250, others), so the thread has become a duplicate sink with no progress.This is not a complaint about the bot — it's a request for the issue to be re-scoped at the class level so a fix can land.
Proposed fix (two-part, minimal surgery)
Fix A — comparator symmetry (Layer 2, was Patch E in our local repro)
Make
buildClaudeLiveFingerprint's prompt-derived hash comparison treat "absent / empty" as "no assertion, do not invalidate." One-line semantic change:Same pattern applies to
promptToolNamesHashif it can legitimately be unset on a turn.Rationale (also from the canonical thread): the hash field's semantic intent is "did the static system context change?"
undefined/empty means "caller has nothing to assert this turn," not "the static context is now empty." The current code conflates these and falsely invalidates.We applied Fix A locally on 2026.5.3 in May (single-line dist edit), Boss-verified the orchestrator retained context across announce within 30 minutes of patching. The patch was wiped by the 2026.6 upgrade. We re-verify it works each upgrade and re-apply.
Fix B — caller-side, thread static through announce (Layer 1)
subagent-announce-delivery-*.tsshould populateextraSystemPromptStaticfrom session-bound directChatContext (or pass a sentinel that tells the gateway agent handler to compute the same static parts it would for a normal inbound turn).This is the architecturally correct fix. Without it, Fix A papers over the symptom; with it, the comparator never sees the mismatch in the first place because the announce turn carries the same static prompt content as a user-typed turn.
Both fixes are non-breaking. The comparator change is the safer immediate landing; Fix B is the durable solution.
Acceptance criteria
claude-clirunning a multi-agent pipeline retains turn-local working memory across subagent completion events (no process restart, no rollback to resume-cold).cli execlog showsreuse=reusableon announce-driven turns when binding is otherwise valid; nocli session reset: provider=claude-cli reason=system-prompton every spawn cycle.close … reason=restartper turn).Relationship to existing reports
I'm happy to attach instrumented
[cli-prompt-trace]output and the dist file/line numbers for the relevant function bodies if maintainers want them — say the word.