Skip to content

fix(codex): release a rejected steering mention's working hold promptly - #919

Merged
matt2e merged 1 commit into
mainfrom
codex-steer-failure-doesnt-let-go
Oct 13, 2026
Merged

matt2e merged 1 commit into
mainfrom
codex-steer-failure-doesnt-let-go

Conversation

@matt2e

@matt2e matt2e commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

What

When turn/steer (or the history read that precedes it) fails, the catch path in src/bundled/codex/runtime.ts posts "Steering was not accepted" but never adds the request to active.requests. Its replied promise therefore never resolves, and the workingUntil race falls through to the previous turn's tail. The rejected mention's 👀💬 reactions and local typing linger until that unrelated turn ends; with channel-scoped sessions, a follow-up in another thread keeps that thread showing as typing after the rejection.

The fix: the catch path now calls request.answered() right after posting "Steering was not accepted", so the rejected mention's own hold settles at the rejection. The steered.tail fallback is unchanged and still applies only to accepted steering or to a turn started in its place.

Addresses the P2 review finding from @wesbillman on #860: #860 (comment)

Before / after

codex-steer-rejection-before-after.mp4

Left = before (c6c04d9e^), right = after (c6c04d9e). Both halves run the same scenario; on the right, mention 2's indicator clears at the rejection (0 ms after it) instead of staying on until turn 1 ends (5015 ms after it).

Still at +4 s: on the left, mention 2 (steer) is still showing 👀💬/typing 2.5 s after "Steering was not accepted"; on the right it reads "cleared at +1509 ms · 0 ms after rejection" while mention 1 (turn 1) is still typing on both sides

How it was made: a scratch driver (outside the repo) runs each commit's real CodexRuntime against the test suite's in-memory fake app-server, with turn/steer answered by an RPC error (-32600 "model busy"). Each mention's hold-release timestamp mirrors AgentsService.working()/hold(), which is what ends the 👀💬 reactions and local typing in the app. The video is a real-time replay of the two recorded timelines rendered side by side; it is not a live app capture, and the reaction/typing publication itself was not exercised.

Validation

  • New regression test in src/bundled/codex/runtime.test.ts: holds a turn open, fails turn/steer with an RPC error, and asserts the rejected mention's hold settles while the running turn's hold stays pending and no replacement turn starts; the turn then completes and answers normally. Fails without the fix (expected false to be true on the rejected hold) and passes with it.
  • bin/pnpm vitest run src/bundled/codex: 4 files, 64 tests passed
  • tsc --noEmit: clean

Deferred

  • Agent review
  • Exercising the change in the running app against a real Codex steering rejection
  • Human testing

🤖 Generated with Claude Code

When `turn/steer` or the preceding history read fails, the catch path
posts "Steering was not accepted" but never adds the request to
`active.requests`, so its `replied` promise could not resolve and the
`workingUntil` race fell through to the previous turn's `tail`. The
rejected mention kept its working reactions until that unrelated turn
ended, and with channel-scoped sessions a follow-up in another thread
kept that thread typing after the rejection.

Resolve the request's own `replied` promise via `answered()` as soon as
the rejection is posted. The `steered.tail` fallback still applies only
to accepted steering or a turn started in its place. Fixes the P2 from
#860 (discussion_r4243890351).

Test: hold a turn open, fail `turn/steer` with an RPC error, and assert
the rejected mention's hold settles while the running turn's hold stays
pending and no replacement turn starts; the turn then completes and
answers normally. Fails on the previous runtime (`expected false to be
true` on the rejected hold) and passes with the fix.

Validation (bin/pnpm, Hermit toolchain):
- `vitest run src/bundled/codex`: 4 files, 64 tests passed
- `tsc --noEmit`: clean

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Signed-off-by: Matt Toohey <[email protected]>
@matt2e
matt2e marked this pull request as ready for review October 13, 2026 00:59
@matt2e
matt2e requested a review from a team as a code owner October 13, 2026 00:59

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No further changes requested: the rejection now settles its request’s working hold after publication, without ending the active turn or changing the accepted/replacement-turn fallback. The new regression distinguishes the rejected hold from the still-running turn.

Star Lord’s automated source review via Wes’s account — head c6c04d9e58d10d52c78c717e33cf7ef99a0cc5b4, base 544d0cd5b2a2b79242fc3452b94d04231cec1fbf.

Source-only: no tests or app execution; CI was incomplete at the snapshot. Inspected the attached still, but could not decode the video. Live rejection/reaction/typing behavior and human acceptance remain unverified.

@matt2e
matt2e merged commit c3f6068 into main Oct 13, 2026
23 checks passed
@matt2e
matt2e deleted the codex-steer-failure-doesnt-let-go branch October 13, 2026 06:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants