Skip to content

fix(native-chat): a refused steer leaves its turn as the chat's latest request - #29907

Merged
brennanb2025 merged 1 commit into
mainfrom
brennanb2025/send-lifecycle-refused-steer
Oct 17, 2026
Merged

brennanb2025 merged 1 commit into
mainfrom
brennanb2025/send-lifecycle-refused-steer

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

ELI5

When you send a follow-up message while a Codex chat is still answering (a "steer") and that answer ends without Codex ever taking your follow-up, Orca marks the follow-up as refused. The sidebar's verdict and the "chat finished" notification then talked about that refused follow-up as if it were the chat's latest request, instead of about the answer it was attached to. This makes the answer's turn stay the request the sidebar and notification report.

What Changed

Before: a Codex turn ends while one of your steers is still waiting to be taken. Orca refuses the steer and moves its bubble to the end of the conversation. Because the bubble no longer sits inside the turn, the code that picks "the chat's latest request" took the refused steer as a request of its own. Today that happens when the turn fails: the sidebar's Failed verdict, its time and the completion notification belong to the steer instead of to the turn.

After: a refused steer is never treated as the latest request. The turn it was sent into answers for it, as the comment at the top of this file already said was intended. A message refused at the start of a new turn (not a steer) still reports as a refused request, as before.

Mechanism: latestStructuredAgentSessionRequest already skips a steer while it sits inside its turn. A rejection relocates the message to the thread (placeRejectedMessage), which erased that signal. The saved rejection still records how the message joined the turn (answeredInTurn.via === 'steer'), so the function now reads that instead of the bubble's current position.

Why

This is the first, standalone step of the native-chat send-lifecycle work (stuck "Working" chats). A later step will refuse more Codex steers from saved facts (for example, a steer a hook blocked inside a turn that otherwise succeeded). Without this fix, each of those would turn a successful turn's sidebar verdict into Failed. Landing it first keeps that later change from making the sidebar wrong.

Alternative considered: leave a refused steer inside its turn instead of moving it. Moving it to where it was refused is deliberate (the conversation shows when the refusal happened), and the saved answeredInTurn fact already answers this question without changing placement.

This was carried over from #28630, which was closed because the larger refactor replaces the rest of it.

Linked Issue

Tracked in #28630 (closed; this is its latest-request part).

Visual Proof

The change is in the sidebar verdict and completion notification for a Codex chat whose turn ended with a steer not taken. Screenshots from the real QA run will be added here.

Testing

  • The existing "a steer the provider refused inside a turn that then succeeded" case in structured-agent-session-latest-request.test.ts now models the saved data as it really is (the refused steer moved to the thread, with answeredInTurn.via: 'steer'). Before this fix the case only passed because its fixture kept the steer inside the turn, which a real rejection never does.

  • CI runs the test, lint and typecheck.

  • I manually tested these changes locally

  • Automated tests added/updated, or explained why not below

Review

Agent skill upstream boundary

  • Not applicable, or this change copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

Remote and mixed versions: reads a field (answeredInTurn) that hosts already save. Rows from older hosts lack it and behave exactly as today. No wire or saved-data change. Platform-independent.

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred)

…t request

A steer Codex answered into a turn that then ended without taking it is
rejected and moved to the thread, so the sidebar verdict and the
completion feed treated it as a refused request of its own and reported
it in place of the turn it joined.
@brennanb2025
brennanb2025 marked this pull request as ready for review October 17, 2026 01:38
@coderabbitai

coderabbitai Bot commented Oct 17, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 2 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 10 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: f3f9cdfc-f505-4b14-9f88-c7b654dc811a
📥 Commits

Reviewing files that changed from the base of the PR and between fd1c6a5 and 1d13c2b.

📒 Files selected for processing: 2
  • src/shared/structured-agent-session-latest-request.test.ts
  • src/shared/structured-agent-session-latest-request.ts
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@brennanb2025

Copy link
Copy Markdown
Contributor Author

A follow-up sent while Codex was replying could be treated as a new failed request when rejection moved its bubble outside the turn. The sidebar and completion notification then attributed the outcome to that refused follow-up instead of the turn it joined.

This PR uses the existing saved steer association to keep the turn as the reported request. A failed turn still reads Failed, with its own date and completion identity; a successful turn with a refused steer retains its success. Smart sort also uses the turn's completion date for ordering and age. The refusal bubble and ordinary start failures keep their existing behavior. No saved-data or wire format changes are introduced; older hosts without the association retain their behavior.

Review: one correctness round, an independent architecture challenge, and the final readiness/performance review completed on 1d13c2b1af7a5d7297eb9925e5d26d219d40dba0, with no PR-caused findings or fixes required. An inherited concern is deferred: a constructed interrupted-turn history with a relocated refused steer suppresses Continue on both main and this branch; a real producer sequence for that combination remains unproven and is outside this presentation fix.

Verification:

  • The full PR workflow, including its aggregate gate, static analysis/typecheck, five unit-test shards and macOS/Windows packaging, passed on the reviewed head. No unresolved review threads; merge state CLEAN; out of draft.
  • Real signed-in Codex QA ran on the M4Air with main 710120991fd2b8063106ba87176278253c0a045c and the exact PR head. Workspace setup, an accepted steer ending Done, an untaken steer followed by Stop ending Interrupted, and final active-status sanity passed. The same composer/Stop sequence was used on both.
  • The changed failed-turn/refused-steer case was not reproduced live. The UI rejected nonexistent models before a turn could start; the existing behavioral fixture covers the relocated saved shape in green CI. Indexed history was empty; populated history, OS notification content, phone/SSH/mixed-version runtime and large-history performance were not exercised.
  • QA setup deviation: the driver briefly launched terminal Codex before enabling Chat UI (about 2m26s on main and 16s on the PR). The lane owner accepted finishing without a rerun. Hooks/settings fingerprints and authentication-file metadata were unchanged; normal agent bookkeeping changes were reported separately. This was not a fully protocol-compliant run. Owned rigs/processes and admitted ports were cleaned up.

Model timing/wording, promotion-toast timing, and an extra driver call to an internal repository-refresh API were recorded and checked as unrelated variations. No unexpected status regression was observed in the exercised native flows.

For Brennan's review

These are whole-window captures from the real runs. The final row documents why the failed-turn case could not be produced; it is not proof of the attribution fix.

Flow Main PR
Workspace tab order and active terminal Main: Workspace tab order and active terminal PR: Workspace tab order and active terminal
Accepted steer → Done Main: Accepted steer → Done PR: Accepted steer → Done
Untaken steer + Stop → Interrupted Main: Untaken steer + Stop → Interrupted PR: Untaken steer + Stop → Interrupted
Failed-turn attempt blocked by model validation Main: Failed-turn attempt blocked by model validation PR: Failed-turn attempt blocked by model validation

The judgment points are the selector, where semantic turn membership survives rejection relocation, and the existing behavioral fixture, which now models that relocated shape. The common pattern separates turn identity from message placement; the intended difference here is that host-owned status projection continues to support older paired clients without a new wire requirement.

@brennanb2025
brennanb2025 merged commit 1c411db into main Oct 17, 2026
58 checks passed
@brennanb2025
brennanb2025 deleted the brennanb2025/send-lifecycle-refused-steer branch October 17, 2026 20:15
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.

1 participant