Repository navigation
fix(server): provider subagent threads follow their parent into the archive - #330
Conversation
…rchive Archiving or deleting a thread never archived the provider-native subagent threads (Claude Task/Agent children and the like) mirrored under it. Every client hides those threads, but they stayed in every active shell read: on bkt3 on 2026-10-19, 318 of 1,144 active threads were finished subagents of an archived parent. A fork-owned reactor (SubagentArchiveCascade.expbkt3.ts) now: - archives a parent's provider-native subagent children on thread.archived and thread.deleted, and unarchives them on thread.unarchived; - once per start, after activation, archives every provider-native subagent whose parent is archived, deleted or missing, and logs the count. delegate_task children, forks and children with an unfinished run are left alone. Candidates come from one index-backed SQL read; commands go out four at a time. server.ts gains one marked import and one marked layer line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every archive also starts a history export that runs git in the worktree, so
archiving bkt3's ~318 orphaned subagents at once would burst ~1,300-1,600 git
processes right after a deploy.
The startup pass now queues one orphan per pause (default 2 seconds, set with
`make({ startupPause })`), so the archives and their exports spread over ~11
minutes. A separate fiber does the pacing and feeds the same worker that runs
live cascades. Live cascades therefore run between orphans and are not held
for the whole pass, and nothing races them on the same child. Each orphan is
re-checked when its turn comes and skipped if its parent came back. The pass
logs progress once per 50 orphans, then one summary line.
Tests pass a zero pause, and a new test advances TestClock to show one archive
per pause.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…live cascades The "unfinished run" guard could never fire: a provider-native subagent thread has no run (0 of 777 on bkt3). Its liveness lives in its parent's subagent record (orchestration_v2_projection_subagents, one per child thread). An archive now skips a child whose record is pending, running or waiting, using the child_thread_id index. When a provider-native record stops being live, its thread is re-checked and follows an archived parent, so archiving a parent mid-turn no longer leaves its subagents behind until the next restart. Unarchive no longer applies the guard. Live cascades are now paced too: a live archive or unarchive waits until the previous live move is `cascadePause` old (default 500 ms, an option to `make`). A bulk clean-out of parents with 40-60 subagents each therefore no longer bursts history exports. Startup orphans still come one per `startupPause` from the pacing fiber, and they interleave with live work in the shared worker. Each child is re-read just before it moves. Tests seed a live subagent record instead of a run. New tests cover a subagent that finishes after its parent was archived, and a cascade paced on the TestClock. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Merged into |
|
Production check: this change went live through promotion #335 (bkmain |
Problem
Archiving or deleting a thread never archives its provider-native subagent threads (Claude Task/Agent children:
relationshipToParent: "subagent"andcreationSource: "provider"). Clients hide these threads, but the server keeps them in every active-thread ("shell") read. On bkt3 on 2026-10-19, 944 of the 1,228 active shell rows were such threads. All of them were finished, and 469 had an archived or deleted parent. The shell query runs on the event loop, and those dead rows cost about 0.4 s of its 0.8 s.Fix
A new fork-owned reactor,
SubagentArchiveCascade.expbkt3.ts, plus one marked line inserver.ts:pending,runningorwaiting) and archives it when that record finishes.delegate_taskchildren and forks never move. A live cascade moves one child per 500 ms, so a parent with 63 subagents takes about 30 s.thread.archive/thread.unarchivewithserver:subagent-archive-cascade:*,server:subagent-unarchive-cascade:*andserver:subagent-archive-startup:*command IDs.Results of archiving (a review found the first one): the orphans' worktrees (56 on bkt3) become eligible for the slim auto-sweep (minimum age 14 days), as for any archived thread. On stage, the auto-sweep runs in
removemode after 7 days, so about 19 stage worktrees that only orphans protect can be removed within 6 hours after the startup pass (the dirty, untracked and unpushed gates still apply). On bkt3, the sweep runs inslimmode after 14 days, and the first of its 56 worktrees qualifies around 2026-10-26. The web app cannot open an archived thread directly, and the parent is archived too. Unarchiving the parent restores the threads.Known, not fixed here: history export file names use the first 8 characters of the thread ID, and every provider subagent ID starts with
thread:p, so some subagent exports overwrite each other. This existed before this PR (today's clean-out already hit it).How I tested
SubagentArchiveCascade.expbkt3.test.ts(in-memory SQLite, real orchestrator, no sleeps):TestClock🤖 Generated with Claude Code — Claude Opus 5.5 in T3 Code.