Skip to content

[Bug]: After a server restart, Claude background subagents keep running and billing while the thread is told they were cancelled #18646

Closed

Description

@gustavohariel

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)

  1. Run an isolated server: t3 serve --base-dir /tmp/t3-repro --port 3799.
  2. In a Claude thread, ask for one background subagent (run_in_background, Haiku) that runs 12 separate Bash calls, each sleep 20; echo tick-N $(date -u +%T) >> alive.log, and end the turn.
  3. After the first tick, kill -KILL <server pid> (what the service launcher does after its 5 s grace).
  4. Start the server again with the same --base-dir and 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

  1. juliusmarminge commented on Oct 12, 2026

    @juliusmarminge
    Member

    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. terminateChild in apps/server/src/serviceLauncher.ts sends child.kill(signal) to the server pid, then SIGKILL after TERMINATE_GRACE_MS = 5_000. The child is spawned without its own process group. ProviderSessionManager.ts only 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.ts then tells the thread the work was cancelled without checking.

    A full service stop/restart through systemd (KillMode=mixed) or Ctrl+C on t3 serve does take down the whole group. Until there's a fix, if an update hangs, check for leftover claude processes 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.

  2. added
    acceptedfeature request accepted
    bugSomething is broken or behaving incorrectly.
    via-triageFiled through npx t3 triage
    on Oct 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    acceptedfeature request acceptedbugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions