fix(agent-chat): stale "subagents running" indicators — settle non-terminal subagents in the transcript layer#158
Merged
Merged
Conversation
The transcript layer's only running→terminal transition was a terminal
subagent_updated snapshot, so a subagent whose terminal snapshot never
arrived (adapter demux lost track after a sidecar restart/resume) showed
'N subagents running' forever — persisted that way and resurrected on
hydrate. Frontend-only counterpart of the backend SubagentTracker:
- Derive settlement from a parent-scoped tool_result whose tool_use_id
matches the subagent's demux id or parentItemId (spawn call id) —
existing stuck threads self-heal on hydrate replay, no migration.
The Workflow tool's own tool_result likewise settles the run + its
in-flight phase agents.
- Force-settle on session_state_changed{closed|error} and on a new user
turn (queued follow-ups behind an active turn are left alone), even
when streaming was already false.
- New view-only 'interrupted' subagent status (muted amber, non-spinning
glyph, excluded from runningSubagentEntries) for forced settles and
the post-hydrate reconciliation — a dead session never hydrates a
spinner. Every inferred settlement is stamped statusAssumed so a later
real running snapshot (Claude background task_progress ticks) revives
the row, and a real terminal snapshot confirms it.
- Surface user-facing runtime_warnings (provider rate-limit rejection,
enumerated assistant errors) as an inline amber runtime_notice row;
debug warnings stay console-only.
- Harden mergeStatus against a missing/unknown incoming status (treated
as pending) — a status-less snapshot used to leak undefined into the
view and crash SubagentRow at render.
Fixes #153
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.
Fixes #153
Problem
The frontend transcript layer had no settlement path for non-terminal subagents: the only running→terminal transition was a terminal
subagent_updatedsnapshot. When the Claude adapter's demux loses track (sidecar restart/resume), the spawning tool's raw parent-scopedtool_resultflows through and no terminal snapshot ever arrives — so theSubagentActivityBar/SubagentsCardshowed "N subagents running" forever, the thread persisted that way, and reopening the workspace resurrected running spinners on a dead session. Provider rate-limit stops were invisible (runtime_warning→console.warnonly).Fix (frontend-only — the backend
SubagentTrackeralready handles the sidebar dot)tool_resultderivation — a parent-scopedtool_resultwhosetool_use_idmatches a running subagent's demux id or itsparentItemId(spawning call id, now merged fromSubagentSnapshot.parent_item_id) settles it (completed/failed). TheWorkflowtool's owntool_resultlikewise settles the run + its in-flight phase agents. Becausetool_results are persisted, existing stuck threads self-heal on hydrate replay — no migration.session_state_changed{closed|error}and a new user turn (mirror ofSubagentTracker::clear_thread; queued follow-ups behind an active turn are left alone) interrupt running subagents and stop running workflows, even whenstreamingwas already false.interruptedstatus (SubagentViewStatus, never on the wire): muted-amber non-spinning glyph, excluded fromrunningSubagentEntries. Post-replay reconciliation force-settles anything still running — a dead session never hydrates a spinner. Apending_approvalworkflow is preserved (a legitimate resting state, not a spinner).assumedsettles — every inferred settlement stampsstatusAssumed; a later realrunningsnapshot (Claude backgroundtask_progressticks) revives the row, a later real terminal snapshot confirms and clears the flag. Self-healing on live streams and on replay.runtime_warningvisibility — a conservative classifier (runtime-notice.ts) promotes user-facing warnings (rate-limitstatus: "rejected", enumeratedassistant error: …) to an inline amberruntime_noticetranscript row; SDK debug noise stays console-only.statustopending; hand-built payloads can omit it) leakedundefinedinto the view and crashedSubagentRowat render.mergeStatusnow treats missing/unknown incoming status aspending.Verification
npm run checkclean,npm run test2415/2415 (new: settlement/revive/classifier/hydrate-reconciliation coverage; dev-fixture tests deliberately updated — the hydrated seed's still-running subagent now correctly settles tointerrupted).streamSubagents()run still shows the running bar, then settles green end-to-end.should_persist_eventalready persists everything needed).