Skip to content

Keep Claude approval choices across model changes without freezing delivery - #34031

Merged
brennanb2025 merged 8 commits into
mainfrom
brennanb2025/claude-approval-reland
Oct 27, 2026
Merged

brennanb2025 merged 8 commits into
mainfrom
brennanb2025/claude-approval-reland

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 14 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​710 $\color{#cf222e}{\Huge{\mathbf{−}}}$​370 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​340
Prod 22 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​323 $\color{#cf222e}{\Huge{\mathbf{−}}}$​177 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​146

A Claude chat saved as Approve for me could reopen asking for every approval, or keep an earlier approval downgrade after a model change. The previous fix (#30914) was reverted in #33823 after an update could freeze the app with a message waiting. This restores saved approval intent and repairs the recovery and delivery mechanisms behind that freeze. Tracked in STA-10953.

What changes

  • Saved supported approval choices reach Claude without a model-catalog gate. Desktop and phone offer provider-supported modes even before a model listing arrives.
  • Orca records the actual launch choices and the first root initialization. If Claude opened in a different approval mode and the next idle message explicitly changes model, the existing replacement path opens the new model with the saved mode. Unknown startup evidence does not invent a mismatch; the same selected model reuses the process.
  • A message joining actual Claude work keeps that turn's model, effort and Fast choices. An idle message applies its own choices after attachment preparation. Automated mail keeps existing choices. API-error-only output remains correlation evidence without overriding a replacement message's choices.
  • History catch-up closes turns it opens in their original thread. At readiness, running root turns at the current process fence that the live translator does not hold become Interrupted. Unknown live identity does not settle them.
  • Delivery repeats only after journal, child, actual joined-start completion or wake progress. A step that changes nothing parks with the message preserved until a legitimate wake. Already admitted external work still drains during disposal.
  • Partial execution observations no longer save a deferred model pick as the model that ran.

The latest repair keeps main's opener, replay/fold and result paths unchanged. The independent actual-work witness is used only for input choices. Existing quota/error grouping and combined warning detail are preserved. No result registry, saved field, wire format, setting or paired-server minimum is added.

Intended behavior and limits

The common pattern separates requested choices, actual process initialization and live work from restored history. This follows that separation using Orca's existing journal and process ownership.

Orca retains its existing no-new-refusal path when work remains owed: messages go to the running process and an incompatible model transition waits for an idle opportunity. Mail keeps current choices. If the turn ends after preparation but before Claude consumes a joining message, that message can become the next prompt on the retained choices; it is neither refused nor automatically resent. Older CLIs that reject the requested mode keep the existing startup failure behavior; the saved choice is not rewritten.

The bounded quota/ordinary-output ordering proofs match main; they do not prove universal late-result attribution or external execution fate. Later live endings dropped under heavy journal load remain outside this change. Approval display and other lifecycle work remain tracked separately in STA-11280 and STA-11451.

Existing behavior, unchanged here: if Orca is quit or updated while a message has been handed to Claude but Claude has not acknowledged it yet, the message is marked unknown ("provider closed before acknowledgement") and is not resent after restart, so it shows one prompt with no reply. This avoids sending it twice. The graceful close settles it in structured-agent-session-child-exit.ts:229-230 and structured-agent-session-dead-generation-settlement.ts:76,95. Recovery skips it because journal-pending-submission-recovery.ts:24-26 and agent-session-queued-submission.ts:11 treat only messages never handed over as queued. Delivery picks only those (structured-agent-session-start-failure-row.ts:149). These files are identical on main and this branch. A message never handed over survives the update and is delivered once.

Verification

  • Production review round 3 is clean at ae74ff2: correctness, all consumers, fresh whole-mechanism reference comparison and readiness/performance. The fresh end-of-round decider ruled KEEP with no remaining source finding.
  • The latest repair passed 31 tests across three named files, then 66 across five named files, through tc-gate (overlapping batches). Immutable retired-model and quota regressions remain unchanged. The real adapter/SDK input-queue traces preserve main's folded turn and combined warning. Two negative controls fail as expected. Normal commit hook passed.
  • The incident regression uses the actual host with an imported running turn, null live provider turn and pending direct send. It verifies interruption, once delivery, responsiveness and N/10N startup scan bounds. The delivery regression preserves an unwritten pending send through no-op settlement, then delivers once after a real wake; exact joined-flight, drain, disposal and operation bounds are covered.
  • Real Claude and Codex recovery passes carry genuinely unhanded messages from previous build b7fb07c through graceful same-home upgrade to ae74ff2. After restart, each original held queue card was delivered with its ordinary Steer action: one linked accepted handoff and one real completed reply, no retyping. The two journal submission rows are the original plus the linked delivery. This proves manual queue recovery; automatic delivery is not claimed. Composer/tab responsiveness was 83 ms for Claude and 496 ms for Codex. Earlier accepted/unknown markers are not counted as waiting-upgrade passes.
  • Saved Auto survives graceful restart and real model changes. An immutable actual-process firstRootInit observation records requested Auto and actual Auto before/after a Sonnet→Haiku change, with the same process generation. Natural fallback was not reached: the fallback/reopen predicate remains fixture-covered. Earlier A1/A2 replies and same-model process reuse carry from the unchanged flow at 845055c.
  • On ae74ff2, the corrected live Claude case has real streamed text in a running root, an ordinary followup submitted 154 ms after that observation and 424 ms before completion, then one real completed reply. The next idle UI Haiku choice produces a real Haiku reply with Auto retained and an editable composer. Failed sleep/timing attempts are preserved but are not pass evidence.
  • Independent C1/C2 and X1/X2 each produced one real reply with responsive navigation/composer at 845055c; this unaffected evidence carries. Native phone UI at 0400564 with isolated host 845055c offers Auto (Full access remained selected) and disables the live model picker. Correct-config mobile permission tests pass all nine cases. Mid-turn pick not reachable from any shipped UI; covered by the recorded real-output test.
  • The permitted actual-conflict main merge is pushed at 14a562f. Its one execution-recording conflict preserves the reviewed partial-snapshot rule and upstream startup-report protection. Narrow integration review and the prescribed merge hook pass. Full CI is green, including all five unit shards, static analysis/typecheck, cross-version compatibility, packaging, mobile and WSL.

Review

Full path: native chat lifecycle, recovery, ordering, prior P0 incident and revert. Three production review rounds converged with fresh KEEP, whole-mechanism reference comparison and readiness/performance passes; the main integration was checked narrowly. An independent final evidence audit passes the adopted QA scope. No new proven finding is deferred. Current head is 14a562f; actual affected QA head ae74ff2 carries across that main-only merge. The loop status comment includes inspected screenshots, exact limits and the completion marker.

Agent skill upstream boundary

  • Not applicable: this change copies no upstream skill-installer material.

Author X handle: @BrennanKB5

…ithout freezing delivery

A Claude chat saved as "Approve for me" could reopen asking for every approval,
because Orca read the folder's saved model list as launch policy, and a model
change could keep running in a weaker approval mode Claude opened with. The
first fix for this was reverted after it froze the app: restored history left
a turn reading as running, a send was refused against it, and delivery retried
the same send with nothing changed.

- The saved approval choice goes to Claude as-is; the menu offers every mode.
- Orca records the mode Claude actually opened in. A send that picks a new
  model reopens Claude first when that mode differs from the one requested,
  only when the chat owes no work; Orca mail notices never reopen it.
- A message sent while a Claude turn is running joins that turn on its current
  choices; new choices apply at the next new turn. Nothing is refused.
- History catch-up closes every turn it opens, in the thread it opened in.
- At startup, a turn reading as running under the new agent that the agent is
  not running is ended as interrupted.
- Delivery goes round again only after a step shows what it changed (a commit,
  a different child, a start it joined ending, or a wake); otherwise it waits
  for the next change instead of repeating the same step.

Tracked in STA-10953.
@brennanb2025
brennanb2025 marked this pull request as ready for review October 26, 2026 18:19
@coderabbitai

coderabbitai Bot commented Oct 26, 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

No actionable comments were generated in the recent review.

Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 5e724b49-4121-4585-ad79-9157d3cb8366
Commits

Reviewing files that changed from the base of the PR and between 7fc258a and 0400564.

Files selected for processing: 2
  • src/main/native-chat/agent-session-wire/structured-agent-session-claude-opening-send-hold.test.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-delivery-loop.test.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.

Walkthrough

The changes make Claude’s auto permission choice available without model-catalog checks and resolve launch permission from saved settings. Claude session state now records launch intent and root initialization permission data for model-transition checks. The delivery loop tracks per-run progress and coordinates provider starts. Provider startup settles eligible unheld turns. Claude dispatch applies execution options at turn boundaries, and transcript catch-up adjusts turn scoping and suppression.

Priority: Normal

Merge Risk: Low · up to 04005

The change remains mergeable with a bounded test-coverage gap: the catalog test does not confirm that choices remain correct after the listing reaches the hook.

Pre-merge checks: Passed 3 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Description check Warning The description provides detailed scope, behavior, testing, review, and agent-boundary information. It does not follow the required template because ELI5, Why, Visual Proof, AI Disclosure, Notes, and … Add the missing template sections. Include a plain-language ELI5 summary, an explicit Why section, Visual Proof with before/after attachments or N/A and a reason, AI Disclosure or the required internal-contributor handling, Notes, and the…
Passed Passed checks: 3 passed
Check name Status Explanation
Title check Passed The title clearly summarizes the primary change: preserving Claude approval choices across model changes while preventing delivery freezes.
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.
Full details: Description check

Explanation

The description provides detailed scope, behavior, testing, review, and agent-boundary information. It does not follow the required template because ELI5, Why, Visual Proof, AI Disclosure, Notes, and Checklist sections are missing. The Linked Issue requirement is also not met: it references STA-10953 but does not provide an issue link or a Fixes reference.

Resolution

Add the missing template sections. Include a plain-language ELI5 summary, an explicit Why section, Visual Proof with before/after attachments or N/A and a reason, AI Disclosure or the required internal-contributor handling, Notes, and the completed Checklist. Link the actual issue, preferably with Fixes #<issue-number> or the repository’s accepted issue-link format.

Finishing Touches: 💡 2
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
🛠️ Fix failing CI checks: 💡
  • 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.

🧹 Nitpick comments: 1
src/main/native-chat/agent-session-wire/structured-agent-session-launch-permission.test.ts: 1

20-22: Trivial · Maintainability & Code Quality · Quick win

Cover saved Claude accept-edits at launch.

The existing integration test already detects a saved auto value being replaced with ask on the next start. No test covers saved Claude accept-edits at this launch-permission boundary. A regression for that value can pass the current launch-permission test.

Suggested test
   it('delegates saved own settings', async () => {
     for (const provider of ['claude', 'codex']) {
       await expect(launchPermission(provider, { permissionMode: 'own' })).resolves.toBe('own')
     }
   })
 
+  it('delegates saved Claude accept-edits settings', async () => {
+    await expect(
+      launchPermission('claude', { permissionMode: 'accept-edits' })
+    ).resolves.toBe('accept-edits')
+  })
+

Review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: b5d1d7d0-86b4-4c3c-a61d-aedb8e61c8bf
Commits

Reviewing files that changed from the base of the PR and between 34bed94 and a69c409.

Files selected for processing: 14
  • 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-turn-opening.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-claude-opening-send-hold.test.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-delivery-loop-deps.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-delivery-loop.test.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-delivery-loop.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-delivery-progress.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-launch-permission.test.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-outgrown-child.test.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-ran-options.ts
  • src/main/runtime/claude-structured-retired-saved-model.test.ts
  • src/shared/structured-agent-session-permission-option.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 5 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.

🧹 Nitpick comments: 1
tests/e2e/claude-auto-mode-host-catalog.unit.test.ts: 1

222-224: Trivial · Functional Correctness · Quick win

Wait for a catalog-derived hook update before the final assertion.

The direct mocks.rpc(undefined, 'agentSession.modelCatalog', { waitForListing: true }) waits for the service response. It does not wait for useHostModelCatalogUpgrade to apply that response and for React to commit the new optionSnapshot. Since the final assertion already matches the pre-listing snapshot, it can pass without checking the after-listing state.

Add a waitFor for a catalog-derived hook change before asserting that the permission choices remain unchanged. This test is explicitly intended to cover the choices both before and after the background listing lands.


Review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 56806e01-559f-4e05-b11d-bc079559a505
Commits

Reviewing files that changed from the base of the PR and between a69c409 and 845055c.

Files selected for processing: 10
  • src/main/native-chat/agent-session-wire/structured-agent-session-adapter-router.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-adapter.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-delivery-loop-deps.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-delivery-loop.test.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-delivery-loop.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-host-delivery.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-launch-permission.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-outgrown-child.test.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-outgrown-child.ts
  • tests/e2e/claude-auto-mode-host-catalog.unit.test.ts
Files with no reviewable changes: 1
  • src/main/native-chat/agent-session-wire/structured-agent-session-outgrown-child.test.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.

@brennanb2025
brennanb2025 marked this pull request as draft October 27, 2026 03:18
@brennanb2025
brennanb2025 marked this pull request as ready for review October 27, 2026 03:18
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Claude’s saved Approve for me choice could silently become Ask, or stay downgraded after changing models. The previous fix was reverted after restored history left a turn marked running and delivery retried a waiting message without making progress, freezing the app.

This restores saved approval intent, distinguishes actual process initialization and live work from imported history, and parks delivery when a step makes no progress. A message joining live Claude work retains that turn’s choices; the next idle message applies its own choices. Existing result grouping and combined quota-warning detail are preserved.

The full loop finished in three production review rounds, with the end-of-round fresh decider ruling KEEP. Fixes covered partial execution readback, the attachment-preparation boundary, API-error-only output being mistaken for actual work, and delivery progress/ownership. Fresh correctness, consumer, whole-mechanism common-pattern comparison, readiness/performance and final QA evidence audits passed. No open proven finding or new P2/P3 deferral remains. Pre-existing journal-pressure ending loss, default-option rejection, approval display and unrelated lifecycle work remain outside this change; related work is tracked in STA-11280 and STA-11451.

Verification:

  • The incident regression uses the actual host with imported running history, null live provider turn and pending direct send. It checks interruption, once delivery and responsiveness. No-op settlement preserves the message, parks, then delivers once after a real wake; exact joined startup, drain/disposal and N/10N operation bounds are covered. Negative controls failed meaningfully and were restored.
  • Latest focused batches passed 31 tests in three named files, then 66 in five named files (overlapping batches). The original retired-model and quota regressions remain unchanged; production adapter/SDK-queue ordering proofs preserve main’s folding and warning detail. Mobile permission tests passed all nine cases under the correct mobile config.
  • Real previous-build b7fb07c created genuinely unhanded Claude and Codex inputs. After graceful same-home upgrade to ae74ff2, each original restored card’s ordinary Steer action produced one linked accepted handoff and one real completed reply. The original and linked-delivery submission rows are both retained; no message was retyped. This is manual queue recovery, not an automatic-delivery claim. Tab/composer checks were 83 ms (Claude) and 496 ms (Codex); Codex’s real reply took about 4.57 minutes.
  • Saved Auto survived graceful restart and actual model changes. Immutable process-bound firstRootInit recorded requested/actual Auto before and after the ae74 model change, with process reuse. The corrected ae74 live case submitted its followup 154 ms after observing actual streamed output in a running root and 424 ms before completion; it received one real Sonnet reply. The next idle Haiku pick received one real Haiku reply with Auto retained and an editable composer.
  • Unchanged C1/C2 and X1/X2 each yielded one real reply and responsive navigation at 845055c; saved Auto A1/A2/model-reuse evidence also carries from that head. Native mobile UI at 0400564 used isolated host 845055c: Auto was offered, Full access remained selected, and live model rows were disabled. mid-turn pick not reachable from any shipped UI; covered by the recorded real-output test. Natural Auto fallback/reopening and rare quota ordering remain fixture-covered; neither is claimed as a live incident capture. Earlier unknown/accepted markers and insufficient-timing attempts are preserved and excluded from the waiting/live passes.
  • Full CI is green on current 14a562f, including static analysis/typecheck, all five unit shards, cross-version wire compatibility, packages, mobile and WSL. That main-only merge composes the reviewed execution-snapshot rule with upstream startup-report protection; its narrow integration review passed. Affected QA at ae74ff2 carries across that merge. All owned test apps/providers/leads stopped and QA admissions released.

For Brennan’s review — whole-window captures, visually inspected. Baseline images show the actual previous build, not a manufactured failure. Recovery result images also show the carried two-reply evidence.

Flow Previous build b7fb07c Branch evidence
Claude waiting upgrade Claude previous build: original pending input Claude after upgrade: original card delivered once, C1/C2 replies also visible
Codex waiting upgrade Codex previous build: original pending input Codex after upgrade: original card delivered once, X1/X2 replies also visible
Saved Auto / model change Previous build: saved Approve for me ae74: Auto retained, next idle Haiku reply
Live followup Previous build: live followup ae74: actual partial output and ordinary live followup

Original restored Claude card before Steer; completed corrected live followup; carried Auto model-change A1/A2 replies at 845055c8.

Desktop permission menu (845055c, unchanged UI):

Desktop permission choices

Native phone permission menu (Auto offered, Full access selected) and live picker lock:

Native phone Permissions offers Approve for me

Three code spots where judgment matters: actual work owns joining-message choices, replace only with no work owed, owned progress decides whether to retry or park.

@brennanb2025
brennanb2025 merged commit a0e7fa0 into main Oct 27, 2026
43 checks passed
@brennanb2025
brennanb2025 deleted the brennanb2025/claude-approval-reland branch October 27, 2026 19:56
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