Skip to content

fix(orchestrator): Keep agent output attached after a mid-turn steer - #4547

Closed
mwolson wants to merge 223 commits into
pingdotgg:t3code/codex-turn-mappingfrom
mwolson:fix/orphaned-output-after-early-settle
Closed

fix(orchestrator): Keep agent output attached after a mid-turn steer#4547
mwolson wants to merge 223 commits into
pingdotgg:t3code/codex-turn-mappingfrom
mwolson:fix/orphaned-output-after-early-settle

Conversation

@mwolson

@mwolson mwolson commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Carried from another PR

  • The Waiting implementation through
    fix(orchestrator): Hide Stop after runtime parks is carried for
    #4378 orchestrator-v2-background-waiting.
    Review those commits there, not here.
  • fix(orchestrator): Keep agent output attached after a mid-turn steer and
    fix(orchestrator): Preserve delayed wake summaries belong to this PR.

#4378 orchestrator-v2-background-waiting
is included because both PRs change the same continuation gate in
bufferWakeMessage. On t3code/codex-turn-mapping that gate reads
!isPendingTaskNotification && !isPendingSubagentNotification && message.type !== "result";
the dependency rewrites the surrounding region and adds an
isNativeOpaqueWakeFrame clause that holds assistant and user frames until a
background-task notification has been buffered. Written against the base alone,
this PR's change would conflict with that rewrite and could drop the dependency
clause.

Stacking it makes the intended end state explicit: the two conditions are
additive, so a stranded assistant frame opens a continuation while the
background-notification gating stays intact. If
#4378 orchestrator-v2-background-waiting
merges first, drop its commits from this branch and the remaining diff applies
unchanged.

Summary

Steering a Claude turn while a tool is running settles the run while the agent
is still working. The reply to the steer then has no live turn to attach to, so
it is buffered and, with nothing asking for a continuation, silently dropped:
the thread goes quiet and returns to Ready with the steer unanswered.

There are three independent defects behind that. Fixing only the steering
handoff or only the post-settle wake leaves a real failure standing, so this
fixes the complete path.

Problem and Fix

Problem and Why it Happened Fix
isClaudeActiveSteeringAbortResult matched only terminal_reason: "aborted_streaming", which the CLI reports when a steer interrupts a streaming message. A steer landing while a tool runs is queued instead and delivered when the tool result arrives, and the CLI ends that native turn with aborted_tools, answering the steer in the next one. That result did not match, so the turn finalized while the CLI kept working. isClaudeSteeringHandoffResult recognises a handoff by delivery state: the CLI cut the turn short (aborted_streaming or aborted_tools), or the turn ended cleanly with nothing to say. Each accepted steer swallows exactly one result, so the turn still ends on the result that answers it.
A handoff leaves the turn depending on a native turn the CLI has not opened yet. Nothing else would terminalize it. The wait is bounded. If no frame reaches the turn within 60 seconds it settles as a failure, not a quiet completion: an accepted steer that was never answered is exactly the silence this change exists to stop.
When a run terminalizes early while the query is still live, later assistant text goes to the wake buffer. It previously requested no continuation and could remain stranded until the session recycled. Assistant text requests a continuation immediately when no relevant background work exists. When same-thread background work is wake-eligible, it waits for notification detail with a two-second fallback so output cannot remain stranded.
A same-thread query replacement or an unroutable offer could clear the wait owner without safely handing off the buffered output. Preserve buffered output across same-thread replacement, cancel only the obsolete notification wait, and clear fallback ownership on every offer exit.

A result carrying text still terminalizes the turn even with a steer
outstanding, and max_turns, background_requested, tool_deferred and the
hook reasons are real terminals even as an empty success. Only completed, or
an older CLI that omits the field, means another native turn is coming.

Validation

message_steering_mid_tool is a newly recorded transcript from a real session,
not a hand-written one. It captures the shape the old guard missed: subtype: success, terminal_reason: "aborted_tools", empty text, then a fresh native
turn carrying the answer. Recording it needed a steerAfter: "tool_use" mode in
the replay recorder, waitForTurnItemType on fixture steer steps so the
replayed steer waits for the tool item instead of racing the transcript, and
stream_event acceptance in the replay decoder, since every query sets
includePartialMessages and fixtures recorded before that option have none.

Each major path was confirmed to fail with its corresponding fix reverted:

  • message_steering_mid_tool replay fixture: without the guard fix the steer's
    answer never reaches the projection.
  • Steer handoff keeps the turn alive; a result that answers it still
    terminalizes; max_turns still terminalizes; the bound settles a silent turn.
  • Stranded assistant text requests exactly one continuation, a notification can
    add its summary before the two-second fallback, and post-settle tool frames
    request nothing.
  • Same-thread query replacement preserves buffered output, failed replacement
    does not leak fallback ownership, and the no-background path offers
    immediately.

The focused Claude adapter suite passes 72 tests. vp check, the full
typecheck, and git diff --check pass on the current branch.

Live-tested on a packaged desktop build. Steering during a ~60s command, the run
now stays live 65 seconds past the steer's delivery and ends only after the last
steered reply, where it previously went terminal within about 75ms of delivery.
A second steer sent while the agent is still working is accepted rather than
refused with thread_not_sendable, and 105 status samples across the turn show
one transition, running to completed, with no blank window and no rescue
continuation run. A four-step steered instruction executed in full.

Manual re-test scenarios, in the published guide at
https://nam7nt0rbtm6.postplan.dev:

  • Claude · interrupt, scenario 4 (steer a long turn twice)
  • Claude · interrupt, scenario 5 (Stop while steered work is running)

Current CI Note

Test, Release Smoke, Mobile Native Static Analysis, Cursor Bugbot, CodeRabbit,
and Macroscope Correctness pass on the current head. Check is red only from the
known Vite+ stdout panic while printing warnings.

Macroscope Effect Service Conventions remains red from a claim that the queued
status rationale was removed. The rationale is still immediately above
deriveThreadActivityRun, and the review thread has the exact source
reference.


Note

Medium Risk
Changes core Claude turn lifecycle, wake/continuation gating, and steer counting; mistakes could leave runs stuck, double-terminal, or drop background wake detail, though coverage is heavy.

Overview
Fixes steering during a running tool and assistant text that arrives after an early turn settle so steered answers and late output are not lost on quiet threads.

Steer handoffs: Replaces the aborted_streaming-only guard with isClaudeSteeringHandoffResult, including aborted_tools and empty clean successes. Pending steers are counted per turn; each handoff result is swallowed instead of terminalizing. A 60s bound fails the turn if the CLI never opens the follow-up native turn. finalizeActiveTurn claims the active turn first to avoid duplicate terminals.

Stranded output: After settlement, assistant text in the wake buffer can trigger a continuation (immediate when no wake-eligible background work, otherwise a 2s wait for task notification detail). Post-settle tool frames still buffer without offering early. Same-thread query replacement no longer clears buffered output and continuation state the same way.

Test/replay: Adds message_steering_mid_tool fixture and recording mode active_steering_mid_tool (steerAfter: tool_use, fixture waitForTurnItemType, replay accepts stream_event). Extensive adapter tests cover handoff, timeout, continuations, and process replacement.

Reviewed by Cursor Bugbot for commit f3363da. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Fix agent output detachment after mid-turn steer in ClaudeAdapterV2

  • Replaces the simple aborted_streaming handoff check with a broader classifier in isClaudeSteeringHandoffResult that also handles aborted_tools and empty-text completed results, preventing premature turn terminalization after a mid-tool steer.
  • Tracks steer counts per turn (via a Map instead of a Set) and defers finalization until a real answer arrives or a 60s boundSteeringHandoff timeout fires, at which point the turn fails with steer_handoff_timeout.
  • Detects "stranded" assistant text that arrives after turn settlement and either immediately requests a continuation (no background work) or buffers for up to 2s waiting for a task notification to own the continuation detail.
  • Adds atomic active-turn claiming in finalizeActiveTurn to prevent duplicate terminal emissions.
  • Adds a new message_steering_mid_tool replay fixture and associated test cases covering handoff survival, timeout settlement, stranded output continuation, and process replacement scenarios.

Macroscope summarized f3363da.

@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 029d5a06-1000-43d3-9ff2-adc5a70f010a

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Jul 25, 2026
@mwolson
mwolson marked this pull request as ready for review July 25, 2026 22:55
Comment thread apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
Comment thread apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
Comment thread apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
@macroscopeapp

macroscopeapp Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces significant runtime behavior changes to turn management in the Claude adapter, including new timeout mechanisms, state tracking for pending steers, and claim-based turn finalization to handle mid-turn steering scenarios. The complexity of the orchestration changes warrants human review.

You can customize Macroscope's approvability policy. Learn more.

@mwolson
mwolson force-pushed the fix/orphaned-output-after-early-settle branch from f386094 to 32c2849 Compare July 25, 2026 23:12
@github-actions github-actions Bot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:L 100-499 changed lines (additions + deletions). labels Jul 25, 2026
Comment thread apps/server/src/orchestration-v2/ProviderRuntimeRecoveryService.ts
Comment thread packages/shared/src/orchestrationV2PendingBackgroundWork.ts
Comment thread apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts
@mwolson
mwolson force-pushed the fix/orphaned-output-after-early-settle branch 2 times, most recently from 2e0d977 to 34d8cac Compare July 26, 2026 00:28
Comment thread apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts
@mwolson
mwolson force-pushed the fix/orphaned-output-after-early-settle branch from 34d8cac to c6cb7e6 Compare July 26, 2026 00:59
Comment thread apps/server/src/orchestration-v2/RunExecutionService.ts
@mwolson
mwolson force-pushed the fix/orphaned-output-after-early-settle branch from c6cb7e6 to 48e1a70 Compare July 26, 2026 01:37
Comment thread apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
@mwolson
mwolson force-pushed the fix/orphaned-output-after-early-settle branch from 48e1a70 to a436602 Compare July 26, 2026 02:33
Comment thread apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts
@mwolson
mwolson force-pushed the fix/orphaned-output-after-early-settle branch from a436602 to 3d442e6 Compare July 26, 2026 03:01
Comment thread apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts
@mwolson
mwolson force-pushed the fix/orphaned-output-after-early-settle branch from 3d442e6 to ad1da43 Compare July 26, 2026 03:42
@mwolson
mwolson force-pushed the fix/orphaned-output-after-early-settle branch from ad1da43 to 333efe8 Compare July 26, 2026 12:57
Comment thread apps/server/src/orchestration-v2/RunExecutionService.ts
@mwolson
mwolson force-pushed the fix/orphaned-output-after-early-settle branch 2 times, most recently from fbc8365 to 73f7755 Compare July 26, 2026 13:53
Comment thread apps/server/src/orchestration-v2/RunExecutionService.ts
Comment thread apps/server/src/orchestration-v2/ProviderTurnStartService.ts Outdated
Comment thread apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts
juliusmarminge and others added 17 commits August 10, 2026 18:04
…sPinned

Main owns migration numbering: 036_ProjectionThreadsPinned landed on main,
so the v2 migrations shift from 036-044 to 037-045. Release path runs all
of main's migrations first, then the v2 stack.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Port thread pinning (pingdotgg#5312) into the orchestration-v2 command pipeline:
  thread.pin/unpin commands, thread.pinned/unpinned events, pinnedAt on the
  v2 thread state and projected shells, promotion semantics (pin clears
  settle/snooze, settle clears pin) matching the v1 decider, and client
  pin/unpin operations in the v2 dispatch style.
- Port the regenerated-title context anchoring (pingdotgg#5365) into
  ThreadTitleRegenerationService: pin the first user message ahead of the
  retained tail when the digest is truncated.
- Re-apply the right-panel controls positioning from pingdotgg#5260 to the v2
  ChatView title bar controls.
- Repair merge artifacts: committed conflict markers in BranchToolbar,
  duplicate capability keys, duplicate CommandPalette import, v1 turn
  naming in DiffPanel's focus-refresh effect, onSend signature merge.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Match progress button spacing and single-line height to static git actions
rerere replayed stale resolutions during the rebase and committed nested
conflict markers in several files. Restore the branch-intended v2 shapes
and re-graft main's compatible additions (pending-card opacity comments,
theme-editor keybinding test, mobile scroll re-arm effects from pingdotgg#5566).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…eysetIndex

Main owns migration numbering: 037_ProjectionTurnsKeysetIndex landed on
main (pingdotgg#5493), so the v2 migrations shift from 037-045 to 038-046.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Native subagent observability (pingdotgg#5219), wired per its spec's v2 merge plan:
- getWorkflowScript RPC re-homed onto the v2 WS surface (contracts, rpc
  group, ws handler, auth scope, client atom).
- AgentsPanel fed by the spec's mapper swap: projectedSubagentsToRuntime
  maps orchestration-v2 subagent entities into the panel model;
  deriveAgentPanelModel's v2Projection leg is now live and the v1 fold
  never runs. Agents surface wired into ChatView + RightPanelTabs.
Other ports and reconciliations:
- Shell reconnect-loop fix (pingdotgg#5561) ported into the v2 shell sync
  (same-session resubscribes resume from the in-memory cursor), with the
  cursor-resume regression test adapted to v2 fixtures.
- Mobile end-follow latch (pingdotgg#5566) ported onto the v2 ThreadFeed.
- Claude ede_diagnostic interrupt classification (pingdotgg#5557) ported into
  ClaudeAdapterV2 (aborted_tools/aborted_streaming => interrupted; CLI
  telemetry never becomes the failure banner). pingdotgg#5559 needs no v2 port
  (unknown system subtypes are already ignored).
- Plan sidebar removed from the v2 ChatView/ChatComposer per main's
  plans-fold-into-chat rework (pingdotgg#5558); rightPanelStore stays at main's
  surface set.
- SettingsPanels rebuilt as main's refactored version plus the branch's
  composer-context setting; sidebar snooze respects the time format
  (pingdotgg#4438 follow-through).
- v1-only leftovers deleted: zombie v1 adapters/ingestion/tests the v2
  rewrite removes, the v1-bound transfer-budget CI harness (pingdotgg#5350, needs
  a v2 rebuild), and main's v1 client pagination machinery (pingdotgg#5493 client
  side; the 037 keyset migration is kept — server-side v2 windowing is a
  follow-up).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The rebase kept the LegendList 3.3.3 upgrade and patch from pingdotgg#5449 and the
mobile end-follow latch from pingdotgg#5566, but the v2 MessagesTimeline/ChatView
still carried the branch's blunt any-gesture-breaks-follow listeners.
Port main's pingdotgg#5566 web mechanics onto the v2 follow architecture:

- resolveTimelineIsAtEnd measures the 40px follow re-arm band from real
  geometry (contentLength/scroll/scrollLength minus the composer inset),
  keeping the isNearEnd fallback for older state shapes.
- Follow now breaks only on gestures that can actually leave the live
  edge: upward wheel with overflowing content, touch drags that exited
  the end band, scrollbar drags vs content clicks, and keyboard
  navigation (PageUp/Home/ArrowUp) — previously keyboard scrolling never
  broke follow and the next stream chunk yanked the view back down.
- Listener attach retries across frames so a thread switch cannot mount
  the list without its opt-out listeners.

Deliberately not ported: pingdotgg#5449's shouldRestorePosition disclosure
anchoring and follow-gated maintainScrollAtEnd — the v2 timeline keeps
maintainScrollAtEnd={false} with its own follow scrolls and anchor
system; flipping that core is a separate change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…gdotgg#5449)

Complete the pingdotgg#5449 architecture on the v2 timeline, following the
LegendList author's direction to lean on the list's native mechanisms
instead of app-side scroll layers:

- maintainScrollAtEnd is enabled and owned by LegendList, gated off only
  while the user reads history (liveFollowEnabled), while a sent turn
  anchors near the top (anchoredEndSpace), or during the two-frame settle
  of a fold toggle.
- maintainVisibleContentPosition compensates size changes natively
  ({data, size, shouldRestorePosition}); fold toggles anchor compensation
  to the toggled row via a disclosure anchor key, so the trigger stays
  under the pointer instead of the viewport chasing the end.
- ChatView's hand-rolled streaming follow (double-rAF scrollToEnd on
  every data change) is gone; the app now only owns streaming
  adjustments during anchored-end-space mode, mirroring main.
- timelineLiveFollowEnabled state mirrors the follow refs so the
  render-visible gate switches native follow off when a gesture breaks
  follow and back on when the viewport returns to the end band.

Timeline tests updated to assert the native-ownership invariants.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Keep success feedback visible in the Git action control for 10 seconds
- Move the running elapsed timer into the panel menu slot
…s with v2

Post-rebase reconciliation sweep:
- Sidebar: main's folded Sidebar.tsx/Sidebar.logic.ts adapted to v2 shells
  (latestRun/runtime naming, waiting status instead of monitoring), with
  subagent-thread filtering and main's pinned-reorder helpers re-exported
- Pinned drag reorder (pingdotgg#5581) ported into v2: thread.pin orderKey +
  thread.pin.reorder command, thread.pin-reordered event, Orchestrator fold,
  ProjectionStore/Maintenance, client-runtime commands and shell mapping
- Project favicon (pingdotgg#4849-era) and defaultThreadEnvMode flowed through v2
  contracts (OrchestrationProjectShell, application event payloads)
- ChatView: main's pingdotgg#5592 header props, pull-request right-panel surfaces,
  liveAgentCount badge (pingdotgg#5745) wired into the v2 panel layout
- enableAssistantStreaming -> enableLegacyTokenStreaming rename applied to
  v2 RunExecutionService and replay testkit
- Removed v1 zombies resurrected by the rebase (provider service/reaper/
  ingestion + v1 layer tests, server.test.ts, integration harness)
- routeTree: main's tree + branch's /settings/scheduled-tasks route
- Misc marker-sweep syntax repairs (rpc.ts, entities.ts, localApi.test.ts,
  rightPanelStore.test.ts, GitManager.test.ts, mobile model menu helpers)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… 038-040

Main released ProjectionThreadsPinOrderKey (038),
ProjectionProjectsDefaultThreadEnvMode (039) and
ProjectionProjectFaviconPath (040), so the branch-private v2 stack shifts
up by three. Registry ids were already 41-49; this renames the files and
identifiers to match and updates the ledger expectations and through-id
boundaries in the migration tests (released boundary 37 -> 40).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- a6c9b41 (agents open pasted images): ClaudeAdapterV2 now grants the
  attachments dir alongside cwd via additionalDirectories and appends
  '[Attached ... is saved at: path]' lines to the turn text so tools can
  dereference pasted images (pixels alone are not tool-readable).
- 5bb8c03 (settle leaves monitors running): thread.settle now joins
  archive/delete in the provider-session detach set, so PR monitors, dev
  servers and subagent fleets stop when the user parks the thread. The
  settle guard already rejects active runs, and serialized dispatch closes
  the re-engage race the v1 fix handled with onlyIfSettled.
- e70cdb4 (Claude resume handshakes) and 2c7267a (reaper vs live
  background subagents) are already covered structurally in v2: results
  are turn-scoped with explicit zero-turn handshake drops, and idle
  release is pinned while background work is pending.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e panel-visibility merge

The keep-both merge nested main's plan-surface migration test inside a
branch popover test and dropped the threadPanelVisibilityByThreadKey key
from the migration results. Restore main's test body and include the
branch's (empty) visibility map in the expected persisted shape.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…n text

Follow-up to the pingdotgg#5757 port: start and steer turns now append the
'[Attached ... is saved at: path]' line, so the adapter fixtures assert it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@juliusmarminge
juliusmarminge force-pushed the t3code/codex-turn-mapping branch from 22bd872 to a27c1cc Compare August 10, 2026 17:05
The round-3 reconciliation took main's ChatHeader wholesale and wired its
full prop set, resurrecting the scripts/open-in/git-actions cluster the
branch had deliberately relocated into the thread panel. Restore the
79-line slim header (project favicon + name + thread title) and its
minimal ChatView call. pingdotgg#5592's header actions stay a documented v2
follow-up, as decided in round 2.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
mwolson added a commit to mwolson/t3code that referenced this pull request Aug 10, 2026
Re-apply pingdotgg#4547 stranded-output continuation and the full mid-tool steer
handoff path that pure pingdotgg#5388 checkout dropped on the CTM pin move.
juliusmarminge and others added 3 commits August 11, 2026 13:58
- Add ready, merge, and conflict-resolution actions to the PR row
- Share pull request action and handoff logic with the detail panel
- Fix thread details scrolling and row alignment
Steering a Claude turn while a tool was running settled the run while the
agent was still working. The reply to the steer then had no live turn to
attach to, so it was buffered and, with nothing asking for a continuation,
silently dropped: the thread went quiet and returned to Ready with the
steer unanswered.

Two independent defects produced that.

The steer guard keyed on one exact string. `isClaudeActiveSteeringAbortResult`
matched only `terminal_reason: "aborted_streaming"`, which the CLI reports
when a steer interrupts a streaming assistant message. A steer that lands
while a tool is running is queued instead and delivered when the tool result
arrives, and the CLI ends that native turn with `aborted_tools`, answering
the steer in the next one. That result did not match, so the turn finalized
while the CLI kept working. A newly recorded fixture captures the shape from
a real session: `subtype: success`, `terminal_reason: "aborted_tools"`,
empty text, followed by a fresh native turn carrying the answer.

`isClaudeSteeringHandoffResult` replaces it. While a steer is outstanding, a
result hands off when the CLI cut the turn short (`aborted_streaming` or
`aborted_tools`, including on the error result that is how the first of those
arrives) or when the turn ended cleanly with nothing to say. A result
carrying text has answered something and still terminalizes, so a steer the
CLI absorbs into the running turn does not hold the run open; `max_turns`,
`background_requested`, `tool_deferred` and the hook reasons are real
terminals even as an empty success. Each accepted steer swallows exactly one
result, so the turn still ends on the result that answers it, and the
handoff is decided before anything else reads the result so it cannot seed
the turn's fallback assistant text.

Because a handoff makes the turn depend on a native turn that has not opened
yet, it is bounded: if no frame reaches the turn within 60 seconds it settles
as a failure rather than leaving the run running for the rest of the session.
A failure, not a quiet completion, because an accepted steer that was never
answered is exactly the silence this change exists to stop.

Buffered assistant text could also be stranded. When a run terminalizes
early while the query is still live, later frames go to the wake buffer,
which only requested a continuation for a tracked task notification, a
running subagent, or a result. Ordinary post-settle assistant text asked for
nothing, so it sat in the buffer until the session recycled. Assistant text
now requests a continuation on its own. Tool_use and tool_result frames stay
gated: they are the model working rather than speaking, and the notification
that follows them carries the wake detail.

Also record replay `stream_event` frames, which every query receives since
`includePartialMessages` landed, and allow a fixture to hold its steer until
the target run reports a given turn item so a mid-tool steer can be replayed
deterministically.
@mwolson
mwolson force-pushed the fix/orphaned-output-after-early-settle branch from 6025800 to f3363da Compare August 11, 2026 13:23
@juliusmarminge
juliusmarminge force-pushed the t3code/codex-turn-mapping branch from 72e3863 to a186d64 Compare August 11, 2026 17:07
mwolson added a commit to mwolson/t3code that referenced this pull request Aug 12, 2026
Re-apply pingdotgg#4547 stranded-output continuation and the full mid-tool steer
handoff path that pure pingdotgg#5388 checkout dropped on the CTM pin move.
mwolson added a commit to mwolson/t3code that referenced this pull request Aug 12, 2026
Re-apply pingdotgg#4547 stranded-output continuation and the full mid-tool steer
handoff path that pure pingdotgg#5388 checkout dropped on the CTM pin move.
@juliusmarminge
juliusmarminge force-pushed the t3code/codex-turn-mapping branch from a186d64 to 5b1a115 Compare August 12, 2026 23:19
@mwolson

mwolson commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

Absorbed into #5459 settled-thread lifecycle. That PR is now the stability consolidation and includes this mid-turn steer output fix on current t3code/codex-turn-mapping.

@mwolson mwolson closed this Aug 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL 500-999 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants