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
- Start a Codex run on a top-level thread so that it is in flight on the shared app-server (
provider-session:provider-instance:codex:shared). In my case it was a lead thread that had just delegated 20delegate_taskchildren. - Make the app-server stop answering requests ([Bug]: Shared Codex app-server backlogs or dies silently; thread/start hangs for hours and new Codex threads never start #24300), then send the thread a message. T3 sends it as
turn/steerwith the run'sexpectedTurnId, and the request is never answered. - Kill the app-server process. T3 spawns a new one within seconds.
- Let more messages arrive for the thread. Here they were the failed children's completion notices.
Expected behavior
When the app-server process behind a run exits, that run reaches a terminal state (failed or interrupted, with a visible reason), so the runs queued behind it can start. The other option is to resume the run on the new process.
Actual behavior
The run stays starting with completedAt: null, and the thread's activeRunId keeps pointing at it. New runs on that thread queue behind it and never start. I watched for several minutes after the restart and nothing changed.
The rest of the environment recovered correctly after the restart:
- A new thread that had been waiting since 08:29 started and answered at 09:52:53.
- Another thread whose
thread/resumehad hung went back torunning. - The 20 delegated children running on the old process were marked
failedat 09:52:50.
Only the lead thread stayed stuck. Its state after the restart (from t3_thread_read):
"status": "starting",
"activeRunId": "run:thread:f1546295-…:ordinal:25",
"recentRuns": [
{"ordinal": 29, "status": "queued", "requestedAt": "2026-11-11T09:52:55.219Z", "startedAt": null},
{"ordinal": 28, "status": "queued", "requestedAt": "2026-11-11T09:52:54.737Z", "startedAt": null},
{"ordinal": 27, "status": "queued", "requestedAt": "2026-11-11T09:52:54.515Z", "startedAt": null},
{"ordinal": 26, "status": "queued", "requestedAt": "2026-11-11T09:52:53.531Z", "startedAt": null},
{"ordinal": 25, "status": "starting", "requestedAt": "2026-11-10T23:23:08.629Z", "startedAt": "2026-11-10T23:23:10.941Z", "completedAt": null}
]I also tried t3_thread_interrupt from a linked environment. It was refused with runtime_mode_escalation_denied, because the link has approval-required access and the thread runs with full access. That refusal is expected, but it means only Stop in the UI on that machine can clear the run.
The thread record shows providerInstanceId: claudeAgent and model: claude-opus-5-5, but runs 23–25 ran on codex / gpt-6.1-sol after a provider switch. I mention it in case the run-to-session lookup on exit goes through the thread's current provider instead of the run's.
Impact
Blocks work completely
Version or commit
0.1.7 (322f6544df). That release includes #26188 and #24600.
Environment
- Windows 11 Pro build 26300, AMD Ryzen 9 9950X3D, 125 GB RAM.
- T3 Code desktop 0.1.7 running as an always-on server, used remotely from a Mac and a phone through T3 Connect.
codex-cli 0.166.0(WinGet), with one shared app-server for every Codex thread.- Heavy orchestration: lead threads fan out up to 20 concurrent
delegate_taskchildren (gpt-6-luna,gpt-6.1-sol). The environment has 528 threads, andstatev2.sqliteis 2.3 GB.
Logs or stack traces
# provider event log for the stuck thread (UTC)
2026-11-10T23:23:42Z last item on run 25 (a command_execution)
2026-11-11T00:00:08Z out id 159 thread/resume # never answered (#24300)
2026-11-11T08:15:12Z out id 160 turn/steer expectedTurnId=01a1c7c5-… # never answered
2026-11-11T09:52:50Z app-server killed; 20 delegated children -> failed
2026-11-11T09:52:51Z new app-server spawned
2026-11-11T09:52:53Z runs 26-29 queued behind run 25; run 25 still "starting"Related, but different
- [Bug]: Shared Codex app-server backlogs or dies silently; thread/start hangs for hours and new Codex threads never start #24300 is the app-server that stops answering, which is what put this run in flight with nothing answering it. This issue is about what happens to such a run after the process is replaced.
- fix(server): Codex works again after its app-server is killed #26188 made new Codex work possible after the app-server is killed, and that part worked here. It didn't settle a run that was already in flight.
- A running effect with an expired lease blocks every later run on its thread, and a server restart doesn't clear it #25638 (closed) had a run with an expired lease blocking later runs. Here a server restart wasn't involved, only an app-server restart.
- [Bug]: Input sent to a Claude thread around a provider crash is never delivered (steered message lost; queued message held and overtaken) #22423 covers a Claude steer lost around a provider crash. Here the run holding the queue never terminates.
Workaround
Press Stop on the stuck thread on the machine itself.
I can send a PR with a fix and a test in which the app-server exits while a run is starting with an unanswered steer, then the test checks that the run settles and the queued run starts. Would you take it?
Activity
rafiki270 commentedon Nov 11, 2026
Follow-up: I traced the server state and the code, and the mechanism is narrower than the report above says. The run wasn't stuck because the app-server exited. It was stuck in a steering restart, and that restart can't complete once the app-server behind the old turn has been replaced. Below are a corrected timeline, a test that fails on main (
9ef52de293), and a small fix.Corrected timeline
From
statev2.sqliteon the affected machine (outbox, run attempts and projection tables), in UTC:08:15 and 08:29: two
provider-turn.steereffects were sent to the app-server that had stopped answering ([Bug]: Shared Codex app-server backlogs or dies silently; thread/start hangs for hours and new Codex threads never start #24300).08:33:37: a third message was sent with a
claudeAgentmodel selection. The provider changed, so T3 forced a steering restart instead of steering:superseded.pendingonclaudeAgent(startedAt: null).starting.This is why the thread's
providerInstanceIdshowsclaudeAgent.09:52:50: the app-server was killed, and T3 spawned a new one.
09:52:53–55: both steers failed after 5 attempts.
09:52:58: the
provider-turn.restarteffect (sessionTransition: detach) failed after 5 attempts with this error, and nothing settled the run:ProviderAdapterProtocolError: Provider turn …01a1c7c5… is not active and cannot be interrupted.09:55:23: the user pressed Stop. Run 25 became
interrupted, and runs 26–30 werecancelled. The old Codex provider turn row is stillrunning.Cause (origin/main
9ef52de293)Orchestrator.ts:~5405-5420(selectionMustApplyNow→forceRestart) decides it.Orchestrator.ts:5745-5756puts the run back tostartingwith a new pending attempt.:5911-5928enqueuesprovider-turn.restart.ProviderTurnControlService.ts:292-295), withoutrequestRuntimeRestart. The shared session id now resolves to the respawned process. Because that session is live, the no-live-session shortcut at:277-290is skipped.CodexAdapterV2.ts:7603-7607returns "is not active and cannot be interrupted".interruptAndAwaitTerminaltreats that as a failure, so the restart never reaches detach and start.EffectWorker.ts:42-57treats "is not active" as already stopped, but only forprovider-turn.interrupt. Restarts are excluded on purpose.EffectWorker.ts:816-833marks the outbox rowfailedand stops there. Nothing likesettleStartFailureruns for a restart, so the run staysstartingand blocks the queue.RunExecutionService.ts:~1690-1702) writes only to a run that isrunningon its original attempt. This run wasstartingon attempt 2.boundExecution(EffectWorker.ts:585-600) puts a time limit only ondelegated-tasks.stop. So the restart also sat onturn/interruptto the hung app-server from 08:33 until the kill.Failing test
Added to
apps/server/src/orchestration-v2/SelectionRestart.integration.test.ts, reusingmakeRestartAdapterandProviderReplayHarness.layerWithRegistry(..., { runEffectWorker: false }).The adapter's
interruptTurnbehaves the way Codex does after a respawn: it succeeds withrequestRuntimeRestartand otherwise fails with "is not active". The test does the following:restart_activemessage with a different model.queue_after_active.TestClock.It then checks that the restarted run settles and the queued run starts.
Test code
The test fails every run, after 278 ms. It reproduces the production sequence: 5 "is not active" attempts, and then the run stays
starting.Fix I have locally
In
ProviderTurnControlService.interruptAndAwaitTerminal, I runinterruptTurnthroughEffect.result. When the failure chain is "is not active" (a new helper,isTurnNotActiveFailure), it logs a warning and continues, the same way the no-live-session path does. The restart then detaches and starts the new turn, so the user's message is delivered.The change is limited to the interrupt step. A failed start whose error says "is not active" still isn't swallowed, which keeps the rule in the comment at
EffectWorker.ts:42.Checks with the fix:
SelectionRestart.integration.test.ts: 9 of 9 pass, three runs in a row.ProviderTurnControlService.test.ts,EffectWorker.test.tsandSteeringCompletion.integration.test.ts: 60 of 60 pass.typecheck,lint,fmt --checkand the pre-commit hooks pass.Two follow-ups would make this sturdier. I'd keep each as its own PR unless you prefer otherwise:
provider-turn.restartruns out of retries, settle thestartingrun asfailedwith a visible error, the waysettleStartFailuredoes.interrupted.Not verified:
I'm happy to open the PR for the interrupt fix and its test if you want it.
[-][Bug]: A run in flight when the shared Codex app-server exits stays "starting" forever and blocks every queued run on its thread[/-][+][Bug]: A steering restart whose Codex turn died with the shared app-server never settles; the run stays "starting" and blocks the queue[/+]juliusmarminge commentedon Nov 11, 2026
@rafiki270 Could you share the server log lines (or the
orchestration_v2_effect_outboxrows) for run 25's thread between 09:52:49 and 09:52:56, especially any 'Orchestration effect execution failed' entries with effectTypeprovider-turn.restartorprovider-turn.start, and say whether the first completion notice that reached the lead thread was sent with Claude (the thread's current selection) or Codex? That shows whether a restart steer put run 25 back intostartingand then failed for good against the dead session.