Skip to content

fix(chat): keep resumed Claude messages deliverable after settings reports - #30982

Merged
brennanb2025 merged 13 commits into
mainfrom
brennanb2025/chat-root-cause-architecture
Oct 21, 2026
Merged

brennanb2025 merged 13 commits into
mainfrom
brennanb2025/chat-root-cause-architecture

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 14 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​349 $\color{#cf222e}{\Huge{\mathbf{−}}}$​85 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​264
Prod 19 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​253 $\color{#cf222e}{\Huge{\mathbf{−}}}$​190 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​63

ELI5

After Orca restarted a Claude chat, the next message (and sometimes every message after the first one) showed “Claude didn’t accept this message.” Claude never saw those messages: Orca refused them itself before sending. Users hit it repeatedly; it blocked chats saved with model aliases such as opus[1m].

Regression

Introduced by #28046, merged October 19 at 10:53 PT in commit 53191250e87, and first shipped in v1.4.231; v1.4.232 and v1.4.233 also contain it. That change made Orca forget the model, effort and Fast choices it had already applied whenever Claude reported its model under a different name. A saved opus[1m] always comes back as claude-opus-5-5[1m], so a restarted chat either refused its first message or accepted one message and refused every follow-up. Before #28046 there was no such check, and every one of these messages was delivered.

What changes for users

Restarted and resumed Claude chats send normally again, including chats saved before this fix on an alias like opus[1m] with high effort. Choosing a different model or effort still applies it before the next message, and a real refusal from Claude still shows as a failure. Nothing about saved chats, settings or the data exchanged with remote hosts changes.

What changed (mechanism)

  • Orca keeps two separate records: the choices it actually passed to the agent, and what the agent later reports about itself. Only the first decides whether a message can be sent. A renamed model, a missing settings report, or a model the live list omits can no longer block a send.
  • A message only waits on a setting change the user actually made; a real control failure still refuses that message and keeps the original error in the host log.
  • Main recently started remembering which model and settings a chat actually ran on its first turn. That record now uses only what the agent reported, never a requested value the agent did not confirm, and a leftover partial record from an earlier message is cleared before the next message can change the model (Claude, Codex, ACP agents and Pi share this step).

Why this approach

The bug came from one record doing two jobs: it was both "what we applied" and "what the agent reported", so any differently worded report erased what had been applied and the next send was refused. Keeping applied input separate from reports follows the common pattern and removes the whole failure mechanism for Claude rather than adding an exception for aliases.

Differences from the common pattern

  • Intended: Orca stores only the choices the user actually made (it does not fill in defaults), replays the literal model name on the computer running the chat, keeps extra evidence so it can record what a chat actually ran, and keeps a queued message's choices even if the picker changes later.
  • Temporary, tracked in STA-11451: Claude can still refuse a send when Fast must be applied and the live model list cannot confirm Fast support. Ordinary resumed chats skip this check; it is reachable only with a custom --settings Agent Argument plus saved Fast on a model the list does not identify (the picker never offers Fast for such a model).
  • Temporary, tracked in STA-11443: Codex chats on a model missing from Codex's model list can still be refused.
  • Temporary, tracked in STA-11450: Pi and ACP agents can still refuse after a model-only change.

Linked Issue

User-reported incident (repeated "Claude didn't accept this message." on October 19 and 20). Release cherry-pick tracked in STA-11434.

Visual Proof

Real Electron QA on the exact final head f95df9417730, hidden isolated test copy on macOS, real agents signed in through the team's load balancers. Each chat was created and saved on a build from before this fix (main 3be5039, which still has the bug), then opened on this head, the app was restarted, and two ordinary messages were sent in the same conversation.

Agent Result
Claude (saved opus[1m] / High / Fast off) Pass: 2 real replies after restart; then model and effort changed to Sonnet / Medium in the picker and 2 more real replies
Codex Pass: 2 real replies after restart
Pi Pass: 2 real replies after restart
OpenCode Pass: 2 real replies after restart
OMP Pass: 2 real replies after restart
Grok Not tested: the QA account's Grok usage balance is exhausted (payment required)
Antigravity Not tested: not installed on the QA machine

No "didn't accept this message" appeared for any agent, and every send was accepted. The saved choices stayed the user's literal picks; nothing written back came from an unconfirmed request. Real Claude CLI suites passed 15/15 on this head.

Claude chat saved before the fix, after restart (two follow-ups answered):

final-b-claude-2.png

Claude after changing model and effort:

final-b-claude-4.png

Codex, Pi, OpenCode and OMP chats saved before the fix, after restart:

final-b-codex-2.png
final-b-pi-2.png
final-b-opencode-2.png
final-b-omp-2.png

Not tested (evidence):

final-b-grok.png
final-b-antigravity.png

Testing

  • New regression tests reproduce the incident (resumed opus[1m] / high / Fast off, Claude reporting the resolved model name, missing settings readback, two sends): they fail on Accept messages while any agent starts, and bound each startup step #28046's commit and on main and pass here. A probe of 10 incident combinations is refused 10/10 on main and admitted on this branch; the commit before Accept messages while any agent starts, and bound each startup step #28046 admits all of them.
  • A regression for the leftover-record case (an earlier model-unknown low-effort run followed by a different model at high effort) fails before the final correction and passes after it.
  • Full PR workflow green on the final head; targeted Claude, Codex, ACP and Pi suites pass.
  • Real Claude CLI suites (claude-structured-real-cli*.test.ts): 15/15 on the final head.

Review

Full review path: two broad review rounds, an architecture panel for the main-merge integration, a bounded final correction, and a fresh final review plus reference comparison on the final head with every difference labelled above.

@brennanb2025
brennanb2025 marked this pull request as ready for review October 20, 2026 03:02
@coderabbitai

coderabbitai Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Claude session state now uses appliedInputOptions instead of reusableOptions. Model, effort, and Fast Mode application use updated tracking, catalog, mutation-sequence, and readback rules. Message dispatch accepts a logger, records option-application failures, and preserves rejection behavior. Session initialization and publication no longer reconcile provider reports into reusable options. Tests cover resumed dispatch, literal models, effort readback, Fast Mode handling, execution recording, and retired-model reset.

Priority: ⚪ Pending latest changes

Merge Risk: 🔵 Low · up to 7a135

A later message’s model can be recorded for an earlier message. Fix the recording boundary before merging, or accept the limited risk of inaccurate execution history.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 46.88% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 32 functions across 28 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Title check Passed The title clearly identifies the main change: keeping resumed Claude messages deliverable after settings reports.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Description check Passed The description is detailed and covers the user impact, mechanism, rationale, testing, visual proof, review context, and regression scope. It includes the required major sections, but it does not prov…
Full details: Description check

Explanation

The description is detailed and covers the user impact, mechanism, rationale, testing, visual proof, review context, and regression scope. It includes the required major sections, but it does not provide a direct issue link in the Linked Issue section and omits the template checklist and Agent skill upstream boundary responses. These omissions do not prevent approval because the description is otherwise complete and focused.

✨ Finishing Touches: 💡 1
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
  • 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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 470edbc7-4f06-4953-998e-7a5ac92fac2a
📥 Commits

Reviewing files that changed from the base of the PR and between 99379d1 and 48f44eb.

📒 Files selected for processing: 19
  • src/main/claude/claude-stream-json-connection.test.ts
  • src/main/claude/claude-structured-dispatch-test-support.ts
  • src/main/claude/claude-structured-effort-reporting.test.ts
  • src/main/claude/claude-structured-message-options.test.ts
  • src/main/claude/claude-structured-message-options.ts
  • src/main/claude/claude-structured-model-confirmation.test.ts
  • src/main/claude/claude-structured-model-preflight.test.ts
  • src/main/claude/claude-structured-option-confirmation.test.ts
  • src/main/claude/claude-structured-options.test.ts
  • src/main/claude/claude-structured-options.ts
  • src/main/claude/claude-structured-retired-model.test.ts
  • src/main/claude/claude-structured-retired-model.ts
  • src/main/claude/claude-structured-session-acquisition.ts
  • src/main/claude/claude-structured-session-adapter.ts
  • src/main/claude/claude-structured-session-options.ts
  • src/main/claude/claude-structured-session-publication.ts
  • src/main/claude/claude-structured-session-startup.ts
  • src/main/claude/claude-structured-session-state.ts
  • src/main/claude/claude-structured-spawn-options.ts
💤 Files with no reviewable changes: 3
  • src/main/claude/claude-structured-session-acquisition.ts
  • src/main/claude/claude-structured-session-startup.ts
  • src/main/claude/claude-structured-session-options.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 1 remain after this review.

Comment thread src/main/claude/claude-structured-message-options.test.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to GitHub limitations.

⚠️ Outside diff range comments (1)

🟡 Minor · Use a per-key fence for appliedInputOptions. · claude-structured-options.ts:119-135

src/main/claude/claude-structured-options.ts:119-135
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use a per-key fence for appliedInputOptions.

When a model write awaits setModel, an overlapping effort write advances the shared mutation sequence. The model write can succeed but skip its appliedInputOptions marker. The next dispatch then sends set_model again because applyClaudeMessageOptions does not find the model marker.

Use a per-key sequence for the marker. Keep the existing shared fence for stale same-key writes and other bookkeeping.

Suggested fix
 const OPTION_ORDER = ['model', 'effort', 'fastMode'] as const
 
+const appliedInputOptionSequences = new WeakMap<ClaudeSession, Map<string, number>>()
+
+function nextAppliedInputOptionSequence(session: ClaudeSession, key: string): number {
+  let sequences = appliedInputOptionSequences.get(session)
+  if (!sequences) {
+    sequences = new Map()
+    appliedInputOptionSequences.set(session, sequences)
+  }
+  const sequence = (sequences.get(key) ?? 0) + 1
+  sequences.set(key, sequence)
+  return sequence
+}
+
+function isCurrentAppliedInputOptionSequence(
+  session: ClaudeSession,
+  key: string,
+  sequence: number
+): boolean {
+  return appliedInputOptionSequences.get(session)?.get(key) === sequence
+}
+
 ...
   session.appliedInputOptions.delete(input.key)
   const mutationSequence = ++session.optionMutationSequence
+  const appliedInputOptionSequence = nextAppliedInputOptionSequence(session, input.key)
 ...
-    if (mutationSequence === session.optionMutationSequence) {
+    if (
+      isCurrentAppliedInputOptionSequence(session, input.key, appliedInputOptionSequence)
+    ) {
       session.appliedInputOptions.set(input.key, input.value)
     }

ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 980ec7e7-ac14-4637-93ce-1f373a32ce0f
📥 Commits

Reviewing files that changed from the base of the PR and between 568ac90 and 1c71ab1.

📒 Files selected for processing: 7
  • src/main/claude/claude-structured-message-options.test.ts
  • src/main/claude/claude-structured-message-options.ts
  • src/main/claude/claude-structured-session-adapter-turns.test.ts
  • src/main/claude/claude-structured-session-adapter.ts
  • src/main/claude/claude-structured-session-options.ts
  • src/main/claude/claude-structured-session-publication.ts
  • src/main/claude/claude-structured-session-startup.ts
💤 Files with no reviewable changes: 2
  • src/main/claude/claude-structured-session-startup.ts
  • src/main/claude/claude-structured-session-options.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 6 remain after this review.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 6e71db60-3c6b-4323-a306-0645fb9b54c5
📥 Commits

Reviewing files that changed from the base of the PR and between 1c71ab1 and 2421b19.

📒 Files selected for processing: 14
  • src/main/claude/claude-structured-applied-options.test.ts
  • src/main/claude/claude-structured-execution-observations.ts
  • src/main/claude/claude-structured-execution-recording.test.ts
  • src/main/claude/claude-structured-message-options.test.ts
  • src/main/claude/claude-structured-message-options.ts
  • src/main/claude/claude-structured-options.ts
  • src/main/claude/claude-structured-retired-model.test.ts
  • src/main/claude/claude-structured-retired-model.ts
  • src/main/claude/claude-structured-session-acquisition.ts
  • src/main/claude/claude-structured-session-options.ts
  • src/main/claude/claude-structured-session-startup.ts
  • src/main/claude/claude-structured-session-state.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-message-choices.test.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-ran-options.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 4 remain after this review.

Comment thread src/main/claude/claude-structured-execution-recording.test.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments: 1
src/main/claude/claude-structured-options.ts: 1

178-180: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the duplicate Fast-off cleanup.

Lines 178-180 and Lines 188-190 both delete session.fastModeReportedMutation when the readback reports fastMode === false. The second block already covers the case, because adopted is readClaudeSettingsFastMode(settings) when the key is fastMode. The first block also runs for effort writes, so it is the only path that clears the marker there. Keep one block, and make its scope explicit.


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 1b8a12c7-8de9-43bc-a392-4b63eeb1cca8
📥 Commits

Reviewing files that changed from the base of the PR and between 2421b19 and 7a1351b.

📒 Files selected for processing: 9
  • src/main/claude/claude-structured-execution-recording.test.ts
  • src/main/claude/claude-structured-message-options.ts
  • src/main/claude/claude-structured-options.ts
  • src/main/claude/claude-structured-session-acquisition.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-adapter.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-handover.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-host-delivery.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-message-choices.test.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-ran-options.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 0 remain after this review.

@brennanb2025

brennanb2025 commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor Author

Review loop status: done on 5705788 (f95df94 plus a clean merge of current main)

  • Problem fixed: resumed Claude chats (for example saved as opus[1m] / High) were refused with "Claude didn't accept this message." before the message reached Claude. Regression from Accept messages while any agent starts, and bound each startup step #28046 (first shipped in v1.4.231).
  • Review: full path, two broad rounds. A design panel ruled on the integration with main's new "what a chat actually ran" record, followed by a bounded final correction. A fresh final review of that correction found no new issues. The only review thread is fixed and resolved.
  • Reference comparison: done on the final head. Every difference is labelled in the PR body: intended, or temporary and tracked in STA-11451, STA-11443 or STA-11450.
  • CI: full PR workflow green on this exact head.
  • Real QA on this exact head: chats saved on a build from before the fix, then restarted, with two real replies each. Claude, Codex, Pi, OpenCode and OMP passed; Claude also passed a model and effort change. All 15 real Claude CLI tests passed. Screenshots are in the PR body.
  • Not tested: Grok, because the QA account's usage balance is exhausted, and Antigravity, because it is not installed on the QA machine. Neither agent's own code changed; both only share the generic chat path. The owner approved merging without them.
  • Final main merge: main was merged in because feat(native-chat): Codex chats list and run the skills Codex loaded #30937 and fix(chat): preserve Claude rewind context after rollback #31231 touched the same files after the last green run. The merge was clean, with no overlap in logic (Codex skills reporting and a comment edit). The full PR workflow is green on 5705788. The owner judged the real QA on f95df94 sufficient.

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