fix(sidecar): actually resume the main agent after a background subagent finishes (#891)#904
Open
passion729 wants to merge 1 commit into
Open
Conversation
Background Task/Agent subagents now finish and report back instead of
stranding the conversation, and their progress renders as a stable,
collapsible block.
Sidecar (auto-resume):
- claude-agent-sdk 0.3.x doesn't emit a `background_requested` pause; it
reports `end_turn` while subagents are still running, so closing the
query killed them ("interrupted, state lost"). Track outstanding
`local_agent` tasks by task_id and hold the turn open on a terminal
result until they finish, then end exactly once. Idle-timeout backstop
+ generator-end self-heal guard against hangs.
Pipeline + UI (visibility):
- Fold a subagent run's repeated task_* events into one evolving
MessagePart::Subagent (running/completed/failed/interrupted), folded
under the spawning Task card; relabel process-restart interrupts as
"interrupted" rather than "failed".
- Render the Task/Agent block as a collapsed-by-default <details> so the
thread doesn't jump while a subagent streams; expand to see nested work.
- Never leak the internal "Async agent launched…" launch acknowledgment;
backgrounded agents read as "running in background".
Tests: sidecar hold/resume unit + integration; pipeline snapshot scenarios
(lifecycle fold, interrupt relabel, genuine failure, parallel isolation);
frontend SubagentStatusCard + collapsible block + ack-suppression.
|
@passion729 is attempting to deploy a commit to the Caspian's Team Team on Vercel. A member of the Team first needs to authorize it. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #891 for real. Background
Task/Agentsubagents now finish and the main agent resumes to report back, instead of the conversation stalling. Also makes a backgrounded run legible in the chat.#891 was previously marked fixed by #897, but #897 does not resolve it — it wired up the wrong SDK signal (details below).
Root cause
With
@anthropic-ai/claude-agent-sdk(tested on 0.3.191; same on the current 0.3.195), when the main agent launches a background subagent and then finishes its message, the SDK emits a normal terminalresult(terminal_reason: "completed",stop_reason: "end_turn") while the subagent is still running. The sidecar's event loop treats the first terminalresultas end-of-turn →emitter.end()+query.close(), tearing down the still-live subagent ("interrupted, state lost"). The latertask_notificationthen has no live query to land on, so the main agent never resumes.The subagent's own tool calls actually stream inside the main query's event stream (interleaved
task_progress+ the subagent'stool_uses), so the query just needs to stay open until the subagent is done.Why #897 didn't work
#897 keys on
terminal_reason === "background_requested". That is a realTerminalReason, but per the SDK's own type doc it means the whole query loop was backgrounded — not aTaskrun_in_backgroundsubagent. In the subagent flow the main turn still ends withterminal_reason: "completed"(confirmed in session logs), so #897's filter is never hit for this issue. Upgrading the SDK (0.3.191 → 0.3.195) doesn't change this — theTerminalReasonsemantics are the same.Fix
In
sidecar/src/claude/session-manager.ts:task_id: asystemtask_startedwithtask_type === "local_agent"adds;task_notificationremoves (local_bash/local_workfloware ignored).resultarrives while any subagent is still outstanding, hold the turn open (record usage, keep the result out of the pipeline, don'tend, don't close the input) and keep draining — the subagents finish, emit theirtask_notifications, and the main agent resumes. End exactly once when a genuinely-terminal result arrives with nothing outstanding.end; user Stop/abort is unchanged.The existing
background_requestedpath is kept (harmless) for the whole-query-backgrounded case it was actually meant for.UI (same change set)
task_*lifecycle into one evolving status card (running / completed / failed / interrupted) under the spawning Task card; relabel a process-restart interruption as "interrupted" rather than a misleading "failed".<details>so the thread doesn't jump while a subagent streams.Tests
sidecar/test/background-subagent-hold.test.ts: unit tests for the task-id tracking + hold decision, plus an integration test (controllable stream) proving the turn holds open throughcompletedand ends exactly once after the subagent reports.bun run test(frontend/sidecar/rust),bun run lint(biome + clippy), typecheck — all green.closes #891