fix(orchestrator): Safely admit execution after uncertain selection#11

draft1
contrib/main/provider-log-thread-files
contrib/ctm/execution-selection-admission
openedSep 27, 2026byMMike Olson
View on GitHubTwo-way sync enabledPR last imported 50m ago

What Changed

When a provider start or a model change fails partway, T3 Code stops assuming the provider is running the selection it asked for. The thread's next message is admitted through a fresh session for that thread, following the existing steering policy for pooled providers, instead of reusing a binding whose model may be wrong. The failed prompt is not resubmitted, and other threads sharing the same provider process keep running.

Output a provider buffered for a thread is delivered before that thread's session changes and keeps its original selection. If it cannot be delivered safely, the change is refused with "Earlier background output from this conversation has not been delivered yet. Try again after it arrives."

A change that would replace a provider process while the affected thread's binding still has running background work is refused with "This change would replace a provider process with running background work. Wait for it to finish or explicitly Stop it before trying again." Claude keeps its existing check for its own background agents and commands. Non-destructive changes still go through, including Codex option changes while its own background commands run, and a CLI the user has stopped can be replaced while it exits.

OpenCode 2 reports the background wake-ups it holds for a thread as undelivered output, so those continuation turns are admitted and deliver the reply instead of being refused. When a Claude background command finishes after the turn ended, the wake-up turn Claude starts is admitted as soon as it begins, so its reply lands in a T3 continuation run instead of being refused.

A thread that a rollback reopened after an idle release, with nothing run on it yet, is admitted as a fresh attachment instead of forcing a provider restart, so a message sent during or after the rollback no longer interrupts it. Output a provider holds for a thread is still protected at admission even when T3 has not recorded which selection produced it.

Why

A start or selection change that partially applied left T3 trusting a selection the native session might not have, so later turns could run on the wrong model, restarts could kill a process that still owned background work, and buffered completions could be dropped or attributed to the new selection. A stale detach with an equal timestamp could also close a binding that had just been admitted. Execution evidence is now scoped to the actual runtime and binding, a failure invalidates it, and only a trustworthy start acknowledgment restores it.

Known Limitations

Switching a Claude thread's model or permission policy while a finished background command's notice is still waiting to be delivered drops that notice with the old process; output Claude produces after the switch is still delivered. If Claude's process dies after a background wake-up is offered but before T3 admits it, that wake-up's buffered output is kept until Stop instead of being dropped right away. Claude initialization has no timeout, so a hung CLI start waits until Stop cancels it. Output buffered while an ACP agent such as Grok has not reported its model can be cleared only with Stop. A Codex start with a different selection and no explicit service tier may still restart its session after a failed start. Switching a Claude thread's model before any intervening turn can drop an earlier opaque shell-completion notification.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Added 1 commit

  • fix(orchestrator): Safely admit execution after uncertain selection01e8ce1