Repository navigation
Description
Before submitting
- I searched existing issues and did not find a duplicate.
- I included enough detail to reproduce or investigate the problem.
Area
apps/server
Steps to reproduce
- Configure two Claude provider instances, A and B, with different Claude config directories, so they don't share native history.
- On instance A in Full access, ask Claude to start a background subagent (Agent tool,
run_in_background) that works for a few minutes and then edits a file. Let the turn finish, so the thread is idle while the subagent runs. - Select instance B in the composer and send a message. The thread shows a "Provider handoff" item and the run starts on B.
- Check instance A's
claudeprocess withps, and wait for the subagent to finish.
Expected behavior
When the send hands the thread to B, T3 detaches A's session and stops its process. It already does this when the same switch starts from a queued run or from thread.model-selection.set. If the background subagent should survive, T3 refuses or defers the switch, as discussed in #16000. No work runs on A that the thread doesn't show.
Actual behavior
A's process keeps running and stays bound to the thread. Its session row still reads ready. When the subagent finishes, Claude Code on A starts its own wake turn. T3 offers one continuation. It runs on B for about 5 seconds with no output, because B can't replay A's buffer (#17099). After that, A keeps taking turns on its own. T3 reads them from the stream but shows none of them.
This is an unsupervised Full access session. It changes files with no UI, and it keeps spending A's quota after the user moved the thread off A.
One case, nightly build, times UTC:
- 14:57 T3 writes its last message to A. No
query.closefor A is logged afterwards. - 15:47:40 the user sends with B selected. Handoff from A to B. Only
provider-turn.startis queued. The thread has noprovider-session.detacheffect at all. - 15:51 to 17:05 the provider event log for A's session has 7
task_notification, 6task_startedand 7resultframes. In that time A ran shell commands and made 9 git commits in the workspace. - 17:05:45 A's account reaches its weekly usage limit.
- 17:20 the process was stopped by hand with SIGTERM.
Code on main @ e58a485:
- Confirmed: the send path ignores the release list.
dispatchMessagedetaches the old session only whencanResumeAcrossInstancesis true (Orchestrator.ts#L6492-L6519). Instances with different config dirs getcreate_with_handoff(ProviderSessionTransitionPolicy.ts#L48-L53), andProviderSwitchServiceputs A's live session inreleaseProviderSessionIds(ProviderSwitchService.ts#L180-L193).startNextQueuedRun(L1736-L1741, L1873-L1882) anddispatchThreadMutation(L3381) use that list. The send path does not. A detach would have stopped the process: Claude setssupportsMultipleProviderThreadsPerSession: false, sodetachcallsreleaseEntry(ProviderSessionManager.ts#L2414-L2423). - Suspected: the idle timeout can't catch it. Every event from a session calls
touchActivity(..., { providerActivity: true })(L1946), which also clears the background-work pin (L1226). A session that keeps starting its own wake turns never reaches the 30-minute idle release. - Related, already reported: the wake is offered to the thread's current provider, once per native thread (ClaudeAdapterV2.ts#L6206-L6234). That is [Bug]: Claude background subagents stay Running forever when their wake continuation runs on another provider #17099. PR fix(server): Claude background subagents end when their wake can't run on Claude #17109 drops such wakes. That removes the empty run on B, but A's process still runs.
Impact
Major degradation or frequent failure
Version or commit
0.1.1-nightly.20261016.3190 (9b150f7). The cited code is unchanged on main @ e58a485.
Environment
macOS 26.6.2, T3 Code Nightly desktop. The message was sent from the mobile app; web sends the selection the same way through client-runtime. Claude Code 2.1.301, claude-opus-5-5 on both instances.
Workaround
Wait for background subagents to finish before switching instances. After a switch, stop the old claude process by its PID. Archiving or settling the thread also detaches every bound session.
Suggested fix
- In
dispatchMessage, detach the sessions in the switch plan'sreleaseProviderSessionIdsfor every instance switch, asstartNextQueuedRundoes. Apply the background-work guard that [Bug]: Runtime-mode, instance and worktree changes detach the Claude session and kill live subagents without the background-work check #16000 settles on. - Don't let a session the thread no longer uses extend its own life through provider activity, so a missed detach still ends.
Related: #16000, #17099 and #17109, #16565 and #17096 (same trigger, different symptom).
Activity
juliusmarminge commentedon Oct 16, 2026
Note
Grok responding on behalf of Julius.
Confirmed on main: this is a bug in the send path.
When a sent message switches the thread to another instance,
dispatchMessageonly detaches the old session whencanResumeAcrossInstancesis true, meaning the instances share native history. For instances with different config dirs it starts the run on the new instance and never detaches the old session. It never reads the switch plan'sreleaseProviderSessionIds. The same switch started from a queued run (startNextQueuedRun) or from a selection mutation (dispatchThreadMutation) does detach the sessions in that list. So the oldclaudeprocess stays bound to the thread and can keep taking wake turns that the UI doesn't show.How this relates to other issues:
Until this is fixed, wait for background subagents to finish before you switch instances, or stop the old
claudeprocess after the switch.juliusmarminge commentedon Oct 22, 2026
Reproduced on main b5a226e49c with the web dev client on Linux and Claude Code 2.1.303. Started a native background subagent on instance A, let the parent turn finish, selected B with a separate config/history directory, then sent a message. B replied while A remained alive and its session still read
ready; the send queued no detach effect. The delayed file-write check was interrupted by a dev-server watcher restart. A focused regression test also fails because both sessions remain after the handoff.