fix(desktop): map subagent timeout/error completions to terminal states so spinners stop - #86592
Merged
Merged
Conversation
…73728) The backend emits terminal statuses including 'timeout' and 'error' in subagent.complete payloads, but asStatus() only recognised 'completed', 'failed', 'interrupted', and 'queued'. Unrecognised values fell through to 'running', making timed-out subagents immortal in the active status stack. Fix: map timeout/error to 'failed', cancelled/canceled to 'interrupted'. Nonterminal unknown statuses still default to 'running' for forward compatibility. Fixes #73728
… surface timeout reason Follow-up to the #73728 normalization fix (supersedes the event-agnostic fallback the maintainers flagged as incomplete): - subagent.complete is terminal by definition — an unrecognized status on it now renders as 'failed' instead of falling through to 'running', which would recreate the immortal false-active row for any future backend status (the keep_open request on #73859). - Live events keep the lenient 'running' fallback. - Synthesize a 'Timed out after Xs' summary from duration_seconds when the backend completes with status 'timeout' and no summary, so the failed row explains itself. - Tests: timeout reason synthesis + pruning, event-aware fail-closed vs lenient live fallback (13 total).
…text Review feedback: prev?.summary could shadow the 'Timed out after Xs' reason when a live event had populated it. timeoutSummary() now wins for raw timeout status; add coverage for the missing-duration placeholder.
…ettle as failed Folded-in coverage from PR #85995 (smause): a subagent.complete event whose payload still says 'running' or 'queued' must settle the row as failed — the completion event itself is the source of truth that the child is done.
Two sibling sites still treated only 'completed'/'failed' as terminal: - delegate-model.ts settled result rows as 'completed' for ANY status other than 'failed', so a delegate result row with status 'timeout' or 'error' (the statuses tools/delegate_tool.py actually emits on child timeout or crash) rendered behind a green check. Settled rows now map ok/completed to completed and everything else to failed. - subagents.ts asStatus accepted a literal 'queued' payload status even on a subagent.complete event, leaving the row active forever. The fail-closed branch now runs before the queued fallback, so completion events always settle.
Contributor
૮ >ﻌ< ა ci reviewran on e83e1cb — chore: map contributor email for attribution audit
|
This was referenced Aug 15, 2026
2 tasks
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.
Desktop no longer leaves timed-out or errored subagents spinning as "running" forever — backend terminal statuses (
timeout,error,cancelled/canceled) now map to terminal renderer states,subagent.completefails closed on anything unrecognized, and delegate result rows stop rendering timed-out children behind a green check.Salvages the duplicate PR cluster #80045 / #85493 / #85995 (all fixing the same status-vocabulary mismatch) into one PR, with contributor authorship preserved via cherry-pick.
Changes
apps/desktop/src/store/subagents.tsasStatusnow normalizes backend statuses:timeout/error→failed,cancelled/canceled→interrupted.subagent.completeevent with an unrecognized — or still-active (running/queued) — payload status settles asfailedinstead of leaving a dead row spinning. Live events keep the lenient fallback.duration_secondswhen the backend sends no summary on a hard child timeout, and prefers it over stale progress text.apps/desktop/src/components/assistant-ui/tool/delegate-model.ts(sibling site widened)failedascompleted, so rows the delegate tool settles withtimeout/error(the statusestools/delegate_tool.pyactually emits) rendered as successes. Now onlyok/completedmap tocompleted; everything else rendersfailed.subagents.test.ts(timeout/error/cancelled normalization, synthesized timeout reason, fail-closed vs lenient fallback, active-payload completions, late-event no-revive) and 1 indelegate-model.test.ts(ok/timeout/error/failure row statuses).Validation
vitest run src/store/subagents.test.ts src/components/assistant-ui/tool/delegate-model.test.tseslinton all four touched filestsc --noEmit(touched files)scripts/audit_pr_attribution.py --fixIssue coverage
timeout/errornot terminal): fixed by theasStatusnormalization. ✅failed, prune at themessage.startboundary, and can't be revived by late progress events. ✅running/queued= active), so timed-out rows now leave the active set immediately. ✅Credits
Unifies three community PRs fixing the same bug (authorship preserved via cherry-pick / attributed follow-up commits):
Infographic