# fix\(server\): changing the permission mode no longer ends the running turn · PR \#23082 · pingdotgg/t3code

[View on GitCafe](https://git.cafe/pingdotgg/t3code/pull/23082)

Repository: [pingdotgg/t3code](https://git.cafe/pingdotgg/t3code)

Visibility: public

State: open

Source repository: Unavailable fork

Source branch: t3/runtime-mode-change-keeps-turn

Target branch: main

Head commit: 7d2f9afa530ffaa31e5b7335d2bb59b9f585f01a

[Discussion and reviews](https://git.cafe/pingdotgg/t3code/pull/23082)

[Commits](https://git.cafe/pingdotgg/t3code/pull/23082/commits?format=markdown)

[Files changed](https://git.cafe/pingdotgg/t3code/pull/23082/diffs?format=markdown)

## Description

---

## Problem

Changing a thread's permission mode while the agent is working ends the turn that's running. For every provider that can't switch modes in place (Claude, Grok, Muse, Cursor, Pi, OpenCode 1.x, ACP agents), `thread.runtime-mode.set` detached the live provider session at once with "Runtime mode changed." That killed the process mid-turn, along with any background subagents and shells, and left approvals that were pending at the time stuck as running.

This is reachable from normal use: pick a new mode in the composer and then steer or queue a message while the agent is running, or have an agent call `t3_thread_configure`.

## Change

A mode change now waits for the next turn instead of tearing the session down when the command lands.

- The orchestrator no longer detaches sessions on `thread.runtime-mode.set`. That eager detach was the only thing applying a new mode for these providers, because turn start compared models but not modes.
- `ProviderSessionManager` records the runtime mode each live session opened with. When a turn opens a session whose mode differs, and the provider can't switch in place, it restarts that session and resumes, the same release-then-resume the detach used to cause, but only between turns. Only single-thread sessions lack in-place switching, so no other thread shares the process being replaced.
- Restarting would still end background agents and commands, so it first checks `hasPendingBackgroundWork`. If work is still running, the turn is refused with a message to wait or press Stop. This matches the guard Claude model changes already get (#14726).

Providers that switch modes in place (Codex, OpenCode 2.x, Antigravity) are unchanged: their sessions were never detached.

## Scope and approval

Addresses the runtime-mode path of #16000, which Julius triaged as the same process kill as #12694 on paths #14726 didn't cover. This PR covers only the runtime-mode trigger; the provider-instance and worktree-handoff triggers in that issue are left as they are.

It also removes the cause of #22957: a mode change no longer fails the run whose approvals are pending.

## Verification

- New `SelectionRestart.integration.test.ts` cases, both failing on `main` and passing here:
  - "a runtime mode change waits for the next turn instead of ending the current one": a Supervised turn on a provider that can't switch in place keeps running after a switch to Auto. The session is not closed mid-turn, and the next turn opens a fresh session and starts in Auto.
  - "a runtime mode change does not end background work to restart the session": with background work pending, the next turn is refused with the guard's message, and the session stays open.
- Related orchestration suites pass: `ProviderSessionManager`, `DispatchModeLimit`, `runtimeLayer`, `Orchestrator.control-reads`, `OpenCode2OrchestratorV2.integration`, `EffectWorker`, `ThreadStop`, `ProviderSwitch.integration`.
- `vp run check:changed` passes.
- I did not exercise this in a running app.

Done with Claude Opus 5.5 in Claude Code, running inside T3 Code.

