Skip to content

fix(subagents): stop showing an interrupted run as still running - #1386

Open
brynne8 wants to merge 1 commit into
agegr:mainfrom
brynne8:fix/subagent-orphaned-status
Open

brynne8 wants to merge 1 commit into
agegr:mainfrom
brynne8:fix/subagent-orphaned-status

Conversation

@brynne8

@brynne8 brynne8 commented Oct 21, 2026

Copy link
Copy Markdown
Contributor

What it does

A subagent run that a server restart cut off keeps a persisted running (or queued) status and no result entry in its session file. The session listing served that raw file status, so the agent switcher and the subagent viewer spun forever on a run that is dead, while the top bar — which reads the live wrappers — said the run had finished.

This PR makes the listing settle such an orphaned run the same way the tool already does:

  • lib/subagents.ts: new settleOrphanedSubagentStatus() — a persisted running / queued status with no result entry and no live run becomes interrupted.
  • lib/session-reader.ts: mapScannedSession() settles the status of the listed row; mergeSessionLists() lets an alive wrapper's runtime row overlay that settled status, so a genuinely queued child still reads as queued.
  • lib/rpc-manager.ts: getRpcSessionInfos() derives the runtime row's status from, in order, a running wrapper, the live run map (getLiveSubagentStatus() in lib/subagent-runtime.ts), or the settled file — never the raw file status alone.
  • lib/subagent-runtime.ts: settleOrphanedRun() now uses the shared helper, and getLiveSubagentStatus() exposes the status of a run this process still holds.

Why it matters (the broken behaviour)

On main, the session LIST disagrees with the get_subagent_result tool:

  • lib/subagents.ts (readSubagentRun()): the persisted status is "queued" or "running" whenever the file has a status entry and no result entry. It is only settled to "interrupted" when the status came from somewhere else.
  • lib/session-reader.ts:258 (mapScannedSession): the listed row takes status: subagent.status — the raw file status — with no check for a run that can no longer finish.
  • lib/rpc-manager.ts:2345 (getRpcSessionInfos): the runtime row's relation.status is session.isRunning() ? "running" : subagent.status — again the raw file status.
  • lib/subagents.ts around line 982 (get_subagent_result): a persisted running/queued with no result and no live wrapper is already reported as interrupted.

So the switcher and the viewer spin on a dead run, the top bar says it is done, and the two views contradict each other. With this PR the list agrees with the tool: the orphaned run reads as interrupted.

How to reproduce (on main, before the fix)

  1. Start a subagent (the built-in subagent switch must be on) and let it run long enough to be dispatched.
  2. Kill the server process (the Node/Next server), so the run is cut off mid-flight. The child's session file keeps a pi-web:subagent-status entry with running and no pi-web:subagent-result entry.
  3. Restart the server and open the agent switcher (or the subagent viewer tab of that child).
  4. Before the fix: the row spins forever on running / queued, while the top bar's badge (which reads the live wrappers) says the run has finished. After the fix: the row reads interrupted, like get_subagent_result reports.

Testing

Node 26.7.0 (nvm use 26.7.0).

Failing on the base commit (e533d9b3), with only the tests added:

$ node --test lib/session-reader.test.mjs lib/rpc-session-info.test.mjs
✖ settles a runtime row whose child was left running by a stopped process (1.196083ms)
  AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
    actual: 'running',
    expected: 'interrupted',
✖ session listing reads a subagent left running by a stopped process as interrupted (15.467875ms)
  AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
    actual: 'running',
    expected: 'interrupted',
✖ a live runtime row keeps its status over the settled disk row (0.197542ms)
  AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
    actual: 'interrupted',
    expected: 'queued',
ℹ tests 38
ℹ pass 35
ℹ fail 3

With the fix, all green:

$ npm test
ℹ tests 2873
ℹ suites 16
ℹ pass 2872
ℹ fail 0
ℹ cancelled 0
ℹ skipped 1
ℹ todo 0
ℹ duration_ms 21586.03025

$ node_modules/.bin/tsc --noEmit
(no output — clean)

$ npm run lint
> @agegr/pi-web@0.12.2 lint
> eslint .
(no output — clean)

The three new tests are the ones named above: the orphaned listing (lib/session-reader.test.mjs), the live runtime row's overlay in the merged list (lib/session-reader.test.mjs), and the settled runtime row (lib/rpc-session-info.test.mjs).

Provenance

Split out of the Bun-running fork https://github.com/brynne8/pi-web-bun, commit 4ddd96ec ("fix(subagents): stop showing an interrupted run as still running"). It was ported by hand onto this repository's current code (the fork's lib/rpc-manager.ts has since diverged); upstream's own structures were kept, and no part of the fork's file was copied wholesale. No new setting, dependency, UI string or subsystem; nothing in pi or in what pi writes to session files changed.

A child interrupted by a server restart keeps a persisted `running` status and
no result entry. `get_subagent_result` already settled that to `interrupted`,
but the session list served the raw file status, so the agent switcher and the
subagent viewer spun forever while the top bar, which reads live wrappers, said
the run had finished.

- Settle a persisted `running` / `queued` status when a subagent row is read
  from disk, through `settleOrphanedSubagentStatus()`.
- Let an alive wrapper's runtime row overlay that status in the merged list, so
  a genuinely queued child still reads as queued.
- Derive the runtime row status from a running wrapper, the live run map, or the
  settled file, instead of trusting the file alone.
- Cover the orphaned listing and the live overlay with tests, and record the rule
  in the subagents note.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant