Skip to content

fix(desktop): normalise timeout/error subagent statuses to terminal (#73728) - #73859

Closed
RelaxJonh wants to merge 1 commit into
NousResearch:mainfrom
RelaxJonh:fix/desktop-subagent-timeout-status
Closed

RelaxJonh wants to merge 1 commit into
NousResearch:mainfrom
RelaxJonh:fix/desktop-subagent-timeout-status

Conversation

@RelaxJonh

Copy link
Copy Markdown
Contributor

Summary

Fixes #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.

Changes

  • apps/desktop/src/store/subagents.ts: asStatus() now maps timeout/errorfailed and cancelled/canceledinterrupted. Nonterminal unknown statuses still default to running for forward compatibility.
  • apps/desktop/src/store/subagents.test.ts: Added regression test covering all four backend terminal status strings (timeout, error, cancelled, canceled) with verification that they are recognised as terminal and pruned by pruneFinishedSessionSubagents().

Root cause

// Before: only recognised 4 statuses, everything else → running
const asStatus = (v: unknown): SubagentStatus =>
  v === 'completed' || v === 'failed' || v === 'interrupted' || v === 'queued' ? v : 'running'

The backend's delegate_tool.py uses timeout as a terminal status, and error paths use error. Both were normalised to running, so pruneFinishedSessionSubagents() kept them forever and they rendered as active in the composer status stack.

Test plan

  • Existing test suite: all pass
  • New regression test: 4 backend terminal statuses → recognised → pruned

…ousResearch#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 NousResearch#73728
@RelaxJonh
RelaxJonh force-pushed the fix/desktop-subagent-timeout-status branch from 68c6985 to 276779a Compare July 30, 2026 02:19
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/desktop Electron desktop app (apps/desktop/*) tool/delegate Subagent delegation sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Jul 30, 2026

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks — this addresses the verified current mismatch: tools/delegate_tool.py:2243-2251 emits subagent.complete with timeout or error, while current Desktop normalization in apps/desktop/src/store/subagents.ts:58-59 turns both into running.

Problems

  • apps/desktop/src/store/subagents.ts:62 still maps every unknown status to running without considering the event type. An unknown terminal value on subagent.complete would therefore remain active and survive the running/queued-only prune at apps/desktop/src/store/subagents.ts:212. This is the future-terminal-status case called out in #73728.

Suggested changes

  • Make normalization event-aware: map unknown values on subagent.complete to failed, while retaining running for unknown nonterminal events. Add the corresponding prune regression test.

Automated hermes-sweeper review.

if (v === 'completed' || v === 'failed' || v === 'interrupted' || v === 'queued') return v
if (v === 'timeout' || v === 'error') return 'failed'
if (v === 'cancelled' || v === 'canceled') return 'interrupted'
return 'running'

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

asStatus() cannot distinguish a progress update from subagent.complete. Please map an unknown value on a completion event to failed (and test pruning it), while keeping unknown nonterminal events as running; otherwise a future terminal backend status recreates the stuck-active-row bug.

@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 30, 2026
@GottZ

GottZ commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

This was generated by AI during triage.

Summary

One PR addresses issue #73728. #73859 fixes the reproduced timeout/error and cancellation normalization paths, but its event-agnostic fallback does not eliminate the underlying failure mode for future unknown completion statuses.

Related pull requests

Suggested consolidation

Keep #73859 open with a salvage path: retain its targeted terminal-status mappings and pruning regression coverage, then make normalization event-aware so an unknown status on subagent.complete maps to failed while an unknown nonterminal status remains running; add a pruning test for the unknown-completion case requested in the contributor review. There are no duplicate PRs to close.

Complex graph

flowchart LR
    classDef open fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
    classDef merged fill:#dcfce7,stroke:#15803d,color:#14532d
    classDef closed fill:#e5e7eb,stroke:#6b7280,color:#1f2937
    classDef unverified fill:#f3f4f6,stroke:#9ca3af,color:#374151
    classDef best stroke-width:3px,stroke:#b45309
    classDef target stroke-width:3px,stroke:#4338ca
    I73728(["issue #73728 (open)"])
    P73859["PR #73859 (open)"]
    P73859 -->|best fix| I73728
    class I73728 open
    class P73859 open
    class P73859 best
    class P73859 target
    click I73728 "https://github.com/NousResearch/hermes-agent/issues/73728"
    click P73859 "https://github.com/NousResearch/hermes-agent/pull/73859"
Loading

Graph: solid arrow = fixes / best fix, dashed arrow = partial or unverified (see edge label); boxed group = PRs duplicating each other; amber border = best fix; indigo border = target; gray node = closed (state tag in the node label).

Cross-PR triage: Reviewed 1 pull request and 1 issue in this complex. Each diff was read against this issue; Assessment working set: 3 kB of PR diffs, 5 kB of issue/PR text, 3 kB of discussion (4 comments), 3 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch.

DavidMetcalfe added a commit to DavidMetcalfe/hermes-agent that referenced this pull request Aug 13, 2026
… surface timeout reason

Follow-up to the NousResearch#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 NousResearch#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).
@DavidMetcalfe

Copy link
Copy Markdown
Contributor

Thanks for the fix — it correctly identifies the normalization gap and maps the known statuses. To help move this forward following the maintainer review (which requested event-aware handling of unknown completion statuses), I've opened #85493 building on top of your work: your commit is cherry-picked in with authorship preserved, and it adds the event-aware fail-closed fallback, timeout summary synthesis, and tests. If you'd prefer to push updates directly to this branch instead, let me know and I'm glad to defer to your PR.

teknium1 pushed a commit that referenced this pull request Aug 15, 2026
… 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).
bobaba76 pushed a commit to bobaba76/hermes-agent that referenced this pull request Aug 27, 2026
… surface timeout reason

Follow-up to the NousResearch#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 NousResearch#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).
melon-xf added a commit to melon-xf/hermes-agent that referenced this pull request Sep 3, 2026
… surface timeout reason

Follow-up to the NousResearch#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 NousResearch#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).
@teknium1

Copy link
Copy Markdown
Collaborator

Landed on main via #86592 (merge b948248), which cherry-picked your normalisation commit as-is — it is on main as a351c17 with your authorship, plus the event-aware fail-closed and timeout-summary follow-ups from #85493. Thanks @RelaxJonh; closing since the branch is fully contained in main.

@teknium1 teknium1 closed this Sep 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/desktop Electron desktop app (apps/desktop/*) P2 Medium — degraded but workaround exists sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state tool/delegate Subagent delegation type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Desktop leaves timed-out subagents stuck as running

5 participants