Repository navigation
fix(native-chat): a Stop Codex took settles the message whose turn never opened (follow-up to #25217) - #26105
Merged
Merged
Conversation
…n never opened When Codex takes a person's Stop on a turn it never opened, no turn-ended event follows, so the message stayed pending: the chat read Working with Stop shown, and the next turn's clock counted from that message. The Stop's settle now handles that case from its own answer: when the provider names the turn it took and that turn has no record, the sends it was for are withdrawn by the same rule a Codex child's end after a Stop already uses. The send's own row then says it was stopped before the agent started, and Working, the clock and the opening-send hold follow from it being settled. Deletes the opening-send hold's special case for a taken Stop's note, which this makes dead, and moves the Stop note key back next to its only users.
…ough the CLI's end, and the next message goes out
…s nothing, and the withdrawal reads from the handover Codex can abort a turn before it starts: it answers the interrupt, then sends turn/completed for a turn that never sent turn/started. The translator wrote an empty interrupted turn for it, so the person saw that turn beside the message's own "Stopped before the agent started" row, and when the end was read before the interrupt's answer the Stop's settle found a record and withdrew nothing. Codex records a turn's prompt only once the turn starts, so an interrupted turn this child never started, with no item or prompt read, now gets no record. Failed ends keep theirs. The withdrawal now asks whether a turn opened since the send's handover row, the same point the opening-send hold reads, rather than since its acceptance: a turn record written in between is not the send's turn.
… is not taken back
…tles-unopened-send
Contributor
Author
|
Status at d16040a: ready for review once CI finishes. The coordinator flips it to ready; this PR must not be merged by an agent. The problem: in a Codex chat, pressing Stop before Codex had started the turn left the message pending forever. The chat stayed on "Working…" with the Stop button showing, and the next turn's clock counted from the stuck message. Now that message is marked "Stopped before the agent started", Send comes back, and the next message runs as its own turn. Screenshots of before and after are in the PR description.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
ELI5
You send Codex a message, then press Stop before Codex has really started on it. Codex says "OK, stopped", but never tells Orca the work began or ended. Orca kept waiting for it, so the chat said "Working…" forever. Now Orca treats Codex's "OK, stopped" as the answer: the message is marked as stopped before the agent started, and the chat is ready again.
What Changed
The problem (found in #25217's live QA, flow r18-1). In a Codex chat, a message was handed to Codex, and Codex accepted it but had not yet started its turn. The person pressed Stop. Codex accepted the Stop and then sent nothing more for that turn. The message stayed "pending" forever, so:
Why: when Codex takes a Stop, Orca ends the stopped turn by marking its records interrupted. A turn that never started has no records, so nothing was marked and the message was never settled. Only one path settled such a message: the Codex process ending after a Stop.
What the person sees now. The stopped message stays where it was sent, with one "Stopped before the agent started" row under it (the existing row from #25051). There is no extra "Cancellation requested." row. Send comes back, the chat stops reading "Working…", and the next message goes out as its own turn with its own timer.
The mechanism. When Codex takes the Stop and names the turn it stopped, and that turn has no record in the chat's journal, Orca withdraws the message the same way it already does when the Codex process ends after a Stop (
withdrawCodexSendsNoTurnOpenedFor). It is one rule with a second trigger, not a second rule. Working, the timer and the hold all follow from the message being settled.Real Codex can also abort a turn before it starts: it answers the Stop, then sends a turn-end for a turn that never sent its start. Orca used to record that as an empty "interrupted" turn. The person then saw an empty turn next to the message's own stop row, and if the end was read before Codex's answer to the Stop, the message was never withdrawn. Codex saves a turn's prompt only once the turn starts, so an interrupted turn this Codex process never started, with no item or prompt read in it, now gets no record. A failed end still gets its record, because that record carries the error.
Deleted: #25217's Stop-note branch in the opening-message hold (
src/shared/structured-agent-session-opening-send.ts) and its unit tests. The message is now settled, so nothing reaches that branch. The Stop-note key moves back next to its only users (structured-agent-session-command-turn.ts, the same text as before #25217).Production lines: +126 / −93 across 9 files. 37 of those lines are moved, not new: the existing stopped-turn settle and the Stop-note key.
Why
The common pattern settles stopped work from the Stop's own successful result, even when no turn ever started. It does not wait for a turn-ended event that may never come. Orca already did this for a turn that had started. This change closes the gap for one that had not.
Alternatives considered:
Differences from the common pattern:
Linked Issue
Follow-up to #25217 (live QA r18-1).
Visual Proof
Live QA on macOS with an isolated, hidden dev build of this branch (d16040a), using a Codex stand-in behind Orca's real Codex adapter.
Before (#25217's head, same flow r18-1): after the Stop, "Cancellation requested." shows, the chat stays on "Working…", and the Stop button never goes away.
After, flow A (same flow; Codex accepts the Stop and never starts the turn): one "Stopped before the agent started" row under the message, no "Cancellation requested.", no "Working…", Send is back.
The next message goes out as its own turn with its own clock ("Worked for 3s"):
After, flow B (real Codex's shape: Codex accepts the Stop, then ends the turn it never started as interrupted). Same row, no empty "Interrupted" turn, and the next message is answered normally:
Unchanged, flow C (the turn opens before the Stop lands): the first message keeps its own turn, and a second message the Stop took back shows its own "Stopped before the agent started" row above "Stopping…":
Unchanged, flow D (Codex refuses the Stop before the turn opens): the first message is not withdrawn, and the next message waits until that turn opens:
Testing
Automated (explicit file lists,
--maxWorkers=2):main, where the message stays pending.tc:nodeandtc:webare clean on currentmain.Live QA on macOS (M4Air, isolated dev build, Codex stand-in behind the real Codex adapter): 4 of 4 flows pass (Codex driver, gpt-5.6-terra).
A (Stop accepted, turn never opened): the message is withdrawn (journal
rejected/cancelled), with no Stop-note row and no turn record, and the next message runs as its own turn.B (accepted, then an interrupted end for a turn never started): same result, and no turn record is written for that turn.
C (the turn opened before the Stop landed): unchanged; the turn ends interrupted.
D (Stop refused before the turn opened): unchanged; the message is accepted, never withdrawn, and the next message reaches Codex only after the turn opened.
Isolation: the app's home and its Codex and Claude config folders were all inside the QA folder, agent-status hooks were off, the stand-ins were pinned by absolute path, and the real
~/.codex,~/.claudeand~/.orcafiles were unchanged from start to finish.I manually tested these changes locally
Automated tests added/updated, or explained why not below
AI Disclosure
Written with Claude (Opus 5.5), reviewed by a separate Claude review pass.
Review
One Claude review round: no blocking findings. It found two real medium issues: an empty interrupted turn appeared when Codex ended a turn it never started, and the withdrawal measured from the message's acceptance instead of its handover. Both are fixed and were confirmed in a second pass.
Agent skill upstream boundary
docs/reference/agent-skill-sharing-upstream-boundary.mdand copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.Notes
Checklist
N/Awith reasonpnpm lint,pnpm typecheck,pnpm test, andpnpm buildpass (or CI will cover; local preferred)Author X handle: @BrennanKB5