fix(server): a queued provider switch no longer ends background work#30401
Problem
When a queued message switches provider or model, startNextQueuedRun detached every live session in the switch plan without probing it for background work. Detaching ends background agents and commands running inside the provider process, such as Claude native subagents and background shells. A send made right away (dispatchMessage) already probes these sessions and refuses with "The previous provider is still running background agents or commands…". The queued path can't refuse, because nobody is waiting on it.
Fix
- Move the probe from
dispatchMessageinto a sharedsessionHasBackgroundWorkhelper. The behavior is unchanged: one session-wide probe for single-thread sessions, otherwise one probe per provider thread, and a failed probe counts as busy. - In the queued start, probe each session the plan would release, then act on the transition type:
create_with_handoff: the queued run gets its own session on another instance with separate native history. Busy sessions stay running, and only idle sessions are detached.restart_and_resume: this is an account switch or a same-instance restart, and the queued run resumes the busy session's native thread. Running both processes on one native thread would fork its history, so the queue is held (queueHeld). The held queue shows Resume, andqueue.resumeprobes again: it starts the run once the work is done, or holds again if it isn't.
The hold reason is only logged. queueHeld has no reason field, so a client sees a held queue with Resume, the same as the other holds. I left adding a reason to contracts and clients out of this fix.
Verification
Two new rows in the existing #20343 table in ProviderSwitch.integration.test.ts queue a switch behind a running turn while the old Claude session reports background work:
- queued hand-off to an instance with separate history: the run completes on the new instance, and the busy session stays
readyand is never closed. - queued account switch with shared history: the queue holds without closing the session. Once the work ends,
queue.resumedelivers the run on the new account and only then closes the old session.
Both rows fail on main, where the busy session is closed, and pass with the fix. The full file passes (46/46), as does vp run check:changed (format, lint, knip, typecheck, tests beside changes).
Done with Claude Opus 5.5 in Claude Code (T3 Code).
🤖 Generated with Claude Code
Checks
Added 1 commit
- fix(server): a queued provider switch no longer ends background workf3edb1d
Approvability
Verdict: Approved at f3edb1d
Macroscope's review found this PR approvable — Queued provider switches now preserve background work, retaining busy sessions during handoffs and holding restarts until they can safely resume. The change reuses existing probes and queue controls without changing product defaults.
You can add or adjust custom eligibility rules. Learn more.
No actionable comments were generated in the recent review.
Walkthrough
The orchestrator now checks provider sessions for background work before detaching them during queued and immediate switches. Integration tests cover queued switches with shared and separate histories.
Changes: Provider session handoff
Layer / File(s) Summary Background-work checks and handoff behavior
apps/server/src/orchestration-v2/Orchestrator.ts,apps/server/src/orchestration-v2/testkit/ProviderSwitch.integration.test.tsA shared probe checks sessions for background work. Queued restart-and-resume switches hold queued runs when a releasable session is busy; other queued switches detach only idle releasable sessions. Immediate dispatch rejects a switch when a session slated for detachment is busy. Integration tests cover queued switches with shared and separate histories, as well as background-work blocking and successful handoff. Estimated code review effort: 3 (Moderate) | ~25 minutes
Change: Bug fix
Possibly related issues
pingdotgg/t3code#16000— The PR adds a background-work check before detaching sessions during provider switches, covering part of the issue’s requested behavior.pingdotgg/t3code#20343— The PR adds background-work checks to queued and immediate switch paths, which relate to the issue’s requested send-path guard.
Suggested labels:
bugfix,size:MSuggested reviewers:
t3dotgg,macroscopeapp
Merge Risk: External image Minimal · up to f3edb
No actionable merge-blocking risk remains from the reviewed provider-switch changes; normal checks can proceed.
Pre-merge checks: Passed 3 · Failed 1
Failed Failed checks: 1 warning
Check name Status Explanation Resolution Description check Warning The description covers the required template sections. Problem gives the failure, the queued path detaching sessions without a background-work probe, and the effects on background agents and shells. C… Add the Scope and approval section. Link the triaged bug issue or the discussion with explicit maintainer approval, including the approval comment. If the fix qualifies as a very small, focused fix of an obvious bug without prior approval, … Passed Passed checks: 3 passed
Check name Status Explanation Title check Passed The title names the main change: queued provider switches no longer detach sessions that still run background work. It follows the conventional commit form and is specific. It is a fix, and the body c… Linked Issues check Passed Check skipped because no linked issues were found for this pull request. Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request. Full details: Title check
Explanation
The title names the main change: queued provider switches no longer detach sessions that still run background work. It follows the conventional commit form and is specific. It is a fix, and the body confirms the probe and hold behavior matches the title.
Full details: Description check
Explanation
The description covers the required template sections. Problem gives the failure, the queued path detaching sessions without a background-work probe, and the effects on background agents and shells. Change explains the shared helper and the per-transition behavior, including the hold for restart_and_resume. Verification names the two new integration test rows, states they fail on main and pass with the fix, and reports 46/46 plus check:changed. The Scope and approval section is missing. It has no linked triaged issue or maintainer approval comment, and no justification for skipping one. The template also asks for the model and harness used, and the description includes the Claude Code attribution, so that item is met.
Resolution
Add the Scope and approval section. Link the triaged bug issue or the discussion with explicit maintainer approval, including the approval comment. If the fix qualifies as a very small, focused fix of an obvious bug without prior approval, explain why it qualifies. The linked issue at git.cafe/pingdotgg/t3code/issues/20343 appears only as the test table reference, so it does not show approval for this fix.
Finishing Touches
Generate docstrings
- Commit to this branch
- Create a new PR
Generate unit tests (beta)
- Commit to this branch
- Create a new PR
- Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
CodeRabbit Verify
- Run CodeRabbit Verify to test the changes in this pull request.
External image Recent review info
-
External image Run configuration
- Source mode: Auto
- Repository: pingdotgg/t3code
- Branch: t3/queued-switch-spares-busy-sessions
- File: .coderabbit.config.ts
- Commit: f3edb1d
- Layers: Repository TS config
- Review profile: CHILL
- Plan: Team
- Run ID:
c2e78e26-594b-4abe-8b85-fcec4f979f92
External image Commits
Reviewing files that changed from the base of the PR and between aee76a8b473694878e820ed642b4b4018991f378 and f3edb1d7ab79b4a56d5edb7e0b4c76b029beef89.
External image Files selected for processing: 2
apps/server/src/orchestration-v2/Orchestrator.tsapps/server/src/orchestration-v2/testkit/ProviderSwitch.integration.test.ts
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 5 remain after this review.
External image Recent review info
-
External image Run configuration
- Source mode: Auto
- Repository: pingdotgg/t3code
- Branch: t3/queued-switch-spares-busy-sessions
- File: .coderabbit.config.ts
- Commit: f3edb1d
- Layers: Repository TS config
- Review profile: CHILL
- Plan: Team
- Run ID:
c2e78e26-594b-4abe-8b85-fcec4f979f92
External image Commits
Reviewing files that changed from the base of the PR and between aee76a8b473694878e820ed642b4b4018991f378 and f3edb1d7ab79b4a56d5edb7e0b4c76b029beef89.
External image Files selected for processing: 2
apps/server/src/orchestration-v2/Orchestrator.tsapps/server/src/orchestration-v2/testkit/ProviderSwitch.integration.test.ts
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 5 remain after this review.
Thread transfer impact
✅ Thread transfer remains within every enforced ceiling.
| Provider | Metric | Main baseline | This PR | Impact | PR ceiling | |
|---|---|---|---|---|---|---|
| Codex | Total thread wire | 5.0 KiB | 5.0 KiB | +35 B (+0.7%) | 6.8 KiB | ✅ |
| Codex | Thread snapshot wire | 3.8 KiB | 3.8 KiB | 0 B (0.0%) | 4.9 KiB | ✅ |
| Codex | Live turn WebSocket wire | 1.2 KiB | 1.3 KiB | +35 B (+2.8%) | 2.0 KiB | ✅ |
| Codex | Live turn WebSocket decoded | 21.5 KiB | 21.6 KiB | +41 B (+0.2%) | 24.4 KiB | ✅ |
| Codex | Live turn messages | 1 | 2 | +1 (+100.0%) | 8 | ✅ |
| Claude | Total thread wire | 5.0 KiB | 5.1 KiB | +17 B (+0.3%) | 6.8 KiB | ✅ |
| Claude | Thread snapshot wire | 3.8 KiB | 3.8 KiB | 0 B (0.0%) | 4.9 KiB | ✅ |
| Claude | Live turn WebSocket wire | 1.2 KiB | 1.3 KiB | +17 B (+1.3%) | 2.0 KiB | ✅ |
| Claude | Live turn WebSocket decoded | 21.9 KiB | 21.9 KiB | 0 B (0.0%) | 24.4 KiB | ✅ |
| Claude | Live turn messages | 2 | 2 | 0 (0.0%) | 8 | ✅ |
Baseline: aee76a8 · PR result: f3edb1d · Source CI: success
Scenario and decoded snapshot size
10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.
- Codex decoded thread snapshot: 108.5 KiB
- Claude decoded thread snapshot: 108.8 KiB
Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.