Repository navigation
[Bug]: After a server restart, Claude background subagents keep running and billing while the thread is told they were cancelled #18646
Description
Area
apps/server (orchestration-v2 restart recovery, Claude provider shutdown)
Summary
I updated T3 from the UI while a Claude thread had 5 background subagents running. After the restart, the thread got the note "the T3 server restarted, and this background work was cancelled before it finished. It will not report back". The subagents were not cancelled. Their transcripts show all 5 kept making API requests for another 4–7 minutes. They ran Bash and edited files, and each one ended mid-tool with no result delivered. Because the thread believed they were dead, it never stopped them. Nobody could see or stop that work, and it used about 12% of my 5-hour Claude limit.
Timeline (2026-10-12, UTC, from ~/.claude/projects/<project>/<session>/subagents/agent-*.jsonl)
| Time | Event |
|---|---|
| 11:35:40–11:36:14 | Main agent launches 5 background subagents (Opus 5.5, run_in_background) |
| 11:38:19 | Updated from the UI; service starts 0.0.46-nightly.20261012.3010 (server-runtime.json startedAt 11:38:22) |
| 11:38:00–11:40:30 | The 5 subagents make 168 requests between them, uninterrupted through the restart |
| 11:40:06 | Thread receives the restart note listing all 5 as cancelled ("will not report back") |
| 11:40:06–11:47:46 | They keep going: 192 requests, ~3.5M price-weighted tokens (≈12% of my 5h limit), 189 Bash calls, 7 file edits |
| 11:44:44–11:47:46 | Each transcript ends with [Request interrupted by user for tool use]. None reports back |
Per agent, after the cancellation note:
| Subagent | Requests | Bash | Edits | Last request |
|---|---|---|---|---|
| Fix staging Pusher NPE | 24 | 24 | 0 | 11:44:44 |
| Fix frameless unhandled rejections | 55 | 48 | 5 | 11:47:46 |
| Fix health-import gate timeout | 36 | 37 | 1 | 11:45:52 |
| Fix onboarding completion error | 57 | 59 | 1 | 11:46:22 |
| Fix explore candidates deadlock | 20 | 21 | 0 | 11:47:19 |
Expected
Either the restart stops the provider process and its subagents (so "cancelled" is true), or recovery detects that they are still running and does not tell the agent they were cancelled.
Actual
The note is written unconditionally (apps/server/src/orchestration-v2/RestartBackgroundNote.ts). Meanwhile the old Claude CLI process survives the server it belonged to. ProviderSessionManager.ts notes that the provider CLIs only stop with the server on "Ctrl+C, or a stop that signals the whole process group". The service launcher signals only the server pid (serviceLauncher.ts: child.kill(signal), then SIGKILL after TERMINATE_GRACE_MS = 5_000).
Steps to reproduce (confirmed on 0.0.46-nightly.20261012.3010)
- Run an isolated server:
t3 serve --base-dir /tmp/t3-repro --port 3799. - In a Claude thread, ask for one background subagent (
run_in_background, Haiku) that runs 12 separate Bash calls, eachsleep 20; echo tick-N $(date -u +%T) >> alive.log, and end the turn. - After the first tick,
kill -KILL <server pid>(what the service launcher does after its 5 s grace). - Start the server again with the same
--base-dirand open the thread.
Result:
tick-1 12:16:36
SIGKILL 12:16:37 -> claude CLI pid survives, reparented (PPID 1261)
tick-2 12:16:59
tick-3 12:17:21
tick-4 12:17:43
server back 12:17:59 -> run stores restartCancelledBackgroundWork:[{"kind":"subagent","label":"alive ticker",...}]
the thread shows the agent under "Previous agents"
tick-5 12:18:04
tick-6 12:18:26
tick-7 12:18:47 -> still running; I killed the orphan by pid here
The subagent transcript shows 6 of its 8 API requests happened after the server was killed. Nothing in T3 could see or stop it any more.
For comparison, a plain SIGTERM that finishes inside the grace period shuts down cleanly. The Claude child dies with it (the subagent's Bash returns exit 137) and the ticks stop. So the bug shows up when shutdown doesn't finish in 5 s, which is likely with several busy Claude threads during an update.
Suggested fix
- Have the launcher signal the whole process group (or have the server spawn provider CLIs so they die with it, e.g.
PR_SET_PDEATHSIG/ a pipe-close watchdog). Then a forced kill can't leave them running. - And/or on startup, find provider processes left by the previous server (the pids are known before shutdown) and kill them before writing "cancelled". Alternatively, only write the note after confirming they are gone.
Related
#14802 (panel shows finished subagents as interrupted after a restart), #15358 (assumes background work dies with the backend), #17847, #9391.
Version
0.0.46-nightly.20261012.3010 (updated from the previous nightly via the UI), Linux, service-managed, Claude Code 2.1.287.
Activity
juliusmarminge commentedon Oct 12, 2026
Note
Grok responding on behalf of Julius.
Confirmed on main: this is a bug. On an in-place UI update, the service launcher stays up and replaces only its child.
terminateChildinapps/server/src/serviceLauncher.tssendschild.kill(signal)to the server pid, thenSIGKILLafterTERMINATE_GRACE_MS = 5_000. The child is spawned without its own process group.ProviderSessionManager.tsonly expects provider CLIs to stop with the server on "Ctrl+C, or a stop that signals the whole process group", and the update path does neither. systemd's cgroup kill doesn't help either, because the launcher doesn't exit during an update. So when shutdown runs past 5 s, the Claude CLI is orphaned.RestartBackgroundNote.tsthen tells the thread the work was cancelled without checking.A full service stop/restart through systemd (KillMode=mixed) or Ctrl+C on
t3 servedoes take down the whole group. Until there's a fix, if an update hangs, check for leftoverclaudeprocesses and kill them.This isn't a duplicate of #15358, #14802 or #17847. Those cover how restarted work is displayed and assumed. This one is about processes surviving the restart.