Skip to content

fix(native-chat): store a chat's permission mode on its own, so a model pick can't reset it - #28268

Merged
brennanb2025 merged 8 commits into
mainfrom
brennanb2025/chat-permission-pick-retain
Oct 14, 2026
Merged

brennanb2025 merged 8 commits into
mainfrom
brennanb2025/chat-permission-pick-retain

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 11 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​84 $\color{#cf222e}{\Huge{\mathbf{−}}}$​78 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​6
Prod 7 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​39 $\color{#cf222e}{\Huge{\mathbf{−}}}$​23 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​16

ELI5

Picking a different model or reasoning effort in a native chat could quietly switch a chat from Full access back to Ask for approval. You never touched the permission button, but the agent started asking before every command again. This PR stores the chat's permission mode on its own, so changing the model can't affect it.

What Changed

Before (on main): set a Codex or Claude chat to Full access, then pick a different model or effort. The button flips to Ask for approval, and the next command stops at an approval card ("Run a command?" or "Allow Bash?"). For Claude, the agent restarts without its skip-permissions flag. This hits new chats as well as old ones.

After: the chat keeps the permission mode you chose through any number of model and effort changes, a chat restart, and quitting and reopening Orca. Anything the agent itself resets on a model change still resets; for example, a new Codex model still drops an effort chosen for the old one.

Mechanism. Each chat's saved record had one options map. It held the agent's settings (model, effort, speed) and also Orca's permission mode under permissionMode. When you pick a model or effort, the agent answers with its own options, and the host saved that answer as the record's whole options map, which deleted the permission mode. Launch then read the missing mode as Ask.

The permission mode is now its own field on the chat record (permissionMode, beside options):

  • Who writes it:
  • Who reads it: launch (structured-agent-session-launch-permission.ts) and the status summary (structured-agent-session-status-summary.ts).
  • What an agent's answer changes: every write of an agent's answer (a model or effort pick, a start report, a resume) replaces only options, so it can't reach the mode any more.

Nothing on the wire changes. Clients still set the mode with setOption{key:'permissionMode'}, and the host publishes it in the same fields as before, so older desktop and mobile clients and remote Orca servers keep working against either version.

Temporary: builds from main since #26116 (the team's hourly builds; no release has them) saved the mode inside options. For those chats, the startup pass moves that saved mode into the new field before it falls back to the agent's launch arguments. Without that step, a chat set to Full access on one of those builds would open as Ask after updating, or the reverse. The step is marked temporary and comes out after 1.4.226 ships.

Why

The common pattern keeps a chat's permission mode as a separate setting from its model selection, each set and resolved on its own, so a model change can't reach the permission mode. An earlier version of this PR instead copied the one key across the agent's answer in the model-pick code (8 lines). That fixed one save path. Any other path that saves an agent's answer, and any future Orca-owned setting kept in the same map, could hit the same bug. A separate field removes the whole class.

It also costs nothing extra now: #26116 and its follow-ups haven't shipped to any user (release hold STA-10426), so no user data has the old shape and no migration is needed. Only the dev-team builds noted above, which the temporary step covers.

Linked Issue

No separate issue. Found while reviewing #28099 and split out of it. Related: #26116, #28099, STA-10345.

Visual Proof

Hidden dev builds on macOS with real Codex and Claude agents. Agent Permissions was set to Manual and new chats default to Ask, so each chat was switched to Full access with its own button.

Before (main): a Codex chat on Full access, after picking a different model, shows Ask for approval.

Before: Codex chat drops to Ask after a model pick

After: the same steps on this branch. After a model pick and an effort pick, the button still shows Full access.

After: Codex chat keeps Full access after model and effort picks

After closing and reopening the chat, and then quitting and relaunching Orca, it still shows Full access. All three commands ran without an approval prompt.

After: Codex chat keeps Full access after restart and relaunch

A Claude chat after switching to Sonnet and High effort also keeps Full access.

After: Claude chat keeps Full access after model and effort picks

Testing

  • Bug test (one, new): structured-agent-session-permission-mode.test.ts › "keeps Full access through a model pick on the running agent and the next start". It runs the real host, record store and journal: set a Codex chat to Full access, send, pick a model on the running agent, restart the host, send again. It expects the restarted agent to launch on Full access. It fails on main, where the relaunch starts on ask.

  • Startup pass: the existing fix(native-chat): chats from before permission modes keep the mode they were running with #28099 backfill tests now check the field. One existing case also covers the temporary step: a dev-build record with the mode inside options keeps that mode, for Ask, Full access and an unrecognised value. That case fails without the step.

  • Other test edits are mechanical: fixtures that seeded options.permissionMode now seed the field. The earlier carry-over test is gone.

  • Runs:

    • all 19 test files under src/main/native-chat and src/main/runtime that mention the permission mode: 226/226 pass;
    • a wider run of src/main/runtime, native-chat, claude, codex, acp, pi, agent-launch and tests/e2e/cross-version-wire: 21,770 tests pass. Six cross-version files timed out on a shared build lock and pass when rerun alone; one Claude-teams launch test fails the same way on main;
    • pnpm tc:node passes; orca-ci-checks --no-typecheck passes, apart from the local Bun tooling step.
  • Live QA, macOS (hidden dev builds, real Codex and Claude, CDP-driven):

    • Main: after a model pick, the mode is deleted from the record and the button shows Ask. Codex shows a "Run a command?" card. Claude restarts without its skip-permissions flag and shows an "Allow Bash?" card.
    • This branch, Codex and Claude: Full access is kept after a model pick and an effort pick, after reopening the chat from Session History, and after quitting and relaunching Orca. All 6 marker writes ran with no prompt (Codex approval_policy never; Claude kept --dangerously-skip-permissions).
    • Old chats from a pre-feat(native-chat): each Claude/Codex chat gets its own permission mode #26116 profile were filled with Full access at startup and wrote with no prompt.
    • A chat set to Full access on a main build showed Ask after updating to the first version of this branch. That led to the temporary step, which its test covers. The live upgrade case wasn't re-run after the step.
  • CI: on 16bf28807ba, the only red check is max-lines on src/main/orcad/orcad-entry.ts (301 lines), which this PR doesn't touch: it came in on main (fix(accounts): launches a WSL host starts itself use the account selected for its distro #28464/fix(orcad): keep Codex status hooks in a managed account home on a WSL host, and list its sessions #28469). The test shards were skipped by fail-fast and will run once main is fixed.

  • 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

  • Cross-platform / SSH / mobile: the record lives on whichever host runs the chat (this Mac, WSL, an SSH host or a paired Orca server), and the wire is unchanged. Not tested live: a mixed old/new pair, mobile on a device, or a real SSH remote. Code was traced and the cross-version wire tests pass.
  • Merge order: merge this before feat(native-chat): each Claude/Codex chat gets its own permission mode #26116 ships in a release. After that, user records would carry the mode inside options, and the temporary step would need to stay.
  • Published answers: a model or effort pick's reply no longer lists the permission mode among the agent's options. Clients only apply the keys a reply returns, so what they show is unchanged.
  • Performance: same number of record writes per pick and per start as main. The startup pass is still one transaction, once.

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)

…rmission mode

A model or effort pick on a running chat replaced the record's options with
the provider's answer, which names only the provider's own options. The
saved permission mode was dropped, and the next start read it as Ask.

The pick now keeps the record's permission mode beside the provider's
answer; everything the provider reset still resets.
@brennanb2025
brennanb2025 marked this pull request as ready for review October 14, 2026 01:32
@coderabbitai

coderabbitai Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

📝 Walkthrough

Permission mode is now stored in the top-level AgentSessionRecord field instead of in provider options. Record creation, option replacement, and permission-mode backfill update that field. Launch and status-summary logic read it from the record. Tests cover persistence, backfill, option changes, and launch after restart.

Priority: ➖ Normal

Merge Risk: 🔵 Low · up to fcb94

Merge Risk: 🔵 Low · up to fcb94

An older chat may launch in Ask mode rather than its saved permission mode if the startup migration write fails. This is a bounded, fail-safe workflow regression; preserve the legacy value as a launch fallback before merging.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 10 functions across 20 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
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.
Title check Passed The title clearly and concisely describes the main change: keeping a chat's permission mode independent from model picks.
Description check Passed The description is detailed and covers the required explanation, changes, rationale, testing, visual proof, compatibility, and checklist items. It references related issues, although it does not use a…
✨ 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.

…rd field

A model or effort pick replaces the record's options with the provider's
answer, which dropped the permission mode saved among them. The mode now
lives beside the options, so no provider answer can touch it. Clients
still pick it with setOption and read it from the same published fields.

@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: 760c5897-b367-4ae6-8a87-bd74c48b5e63
📥 Commits

Reviewing files that changed from the base of the PR and between 865049d and 43a1386.

📒 Files selected for processing: 18
  • src/main/native-chat/agent-session-wire/structured-agent-session-launch-permission.test.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-launch-permission.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-operation-start-refusal.test.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-options-read.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-permission-mode.test.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-registered-definition.test.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-startup-attempt-host.test.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-status-summary.ts
  • src/main/runtime/agent-session-permission-backfill.test.ts
  • src/main/runtime/agent-session-permission-backfill.ts
  • src/main/runtime/agent-session-record-founding.ts
  • src/main/runtime/agent-session-record-options.ts
  • src/main/runtime/agent-session-record-store.test.ts
  • src/main/runtime/claude-structured-startup-unanswered-control-request.test.ts
  • src/main/runtime/rpc/methods/structured-agent-session-create-message.test.ts
  • src/main/runtime/structured-agent-session-runtime.test.ts
  • src/shared/agent-session-record.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.

Comment thread src/main/runtime/agent-session-permission-backfill.ts
…mong options

Hourly builds before the separate field saved the mode in options.permissionMode.
The startup pass now moves it into the field instead of re-deriving it from the
agent's arguments. Temporary; removed after 1.4.226 ships.

@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 · Fall back to the legacy permission mode after… · structured-agent-session-launch-permission.ts:40-48

src/main/native-chat/agent-session-wire/structured-agent-session-launch-permission.ts:40-48
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fall back to the legacy permission mode after backfill failure.

A failed backfill leaves the legacy options.permissionMode persisted, and startup continues. The launch reader now uses only record.permissionMode. A legacy session with a non-ask mode can therefore launch as ask.

Suggested fix
 import type { AgentSessionRecord } from '../../../shared/agent-session-record'
 import {
+  AGENT_CHAT_PERMISSION_MODE_OPTION_ID,
   agentChatPermissionModes,
   chatModelSupportsAutoMode,
   chatPermissionModeNeedsModelSupport,
   effectiveChatPermissionMode,
   type AgentChatPermissionMode
@@
-  const intent = record.permissionMode
+  const intent =
+    record.permissionMode ?? record.options?.[AGENT_CHAT_PERMISSION_MODE_OPTION_ID]

ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 0c1f4400-df3a-45a4-832d-82eae7cdd1c6
📥 Commits

Reviewing files that changed from the base of the PR and between 43a1386 and 16bf288.

📒 Files selected for processing: 2
  • src/main/runtime/agent-session-permission-backfill.test.ts
  • src/main/runtime/agent-session-permission-backfill.ts

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

@brennanb2025 brennanb2025 changed the title fix(native-chat): picking a Codex model or effort keeps the chat's permission mode fix(native-chat): store a chat's permission mode on its own, so a model pick can't reset it Oct 14, 2026
@brennanb2025
brennanb2025 enabled auto-merge (squash) October 14, 2026 03:47
@brennanb2025
brennanb2025 disabled auto-merge October 14, 2026 03:47
@brennanb2025
brennanb2025 merged commit b91795a into main Oct 14, 2026
53 of 55 checks passed
brennanb2025 added a commit that referenced this pull request Oct 14, 2026
…H and headless hosts (#28558)

Picked from PR head fb40193 before merge to main (main's typecheck is broken by #28268 + #27826; fix #28570).
nwparker added a commit that referenced this pull request Oct 14, 2026
#27826 and #28268 crossed: the option-intent writer now needs `now`, and the
message-options context didn't supply it, breaking main's typecheck.

Co-authored-by: Claude <noreply@anthropic.com>
@brennanb2025
brennanb2025 deleted the brennanb2025/chat-permission-pick-retain branch October 14, 2026 05:54
registered2nd pushed a commit to registered2nd/orca that referenced this pull request Oct 14, 2026
…el pick can't reset it (stablyai#28268)

* fix(native-chat): picking a Codex model or effort keeps the chat's permission mode

A model or effort pick on a running chat replaced the record's options with
the provider's answer, which names only the provider's own options. The
saved permission mode was dropped, and the next start read it as Ask.

The pick now keeps the record's permission mode beside the provider's
answer; everything the provider reset still resets.

* chore: re-run CI against current main

* chore: re-run CI against current main

* test(native-chat): keep one permission-mode-survives-pick test

* refactor(native-chat): store a chat's permission mode as its own record field

A model or effort pick replaces the record's options with the provider's
answer, which dropped the permission mode saved among them. The mode now
lives beside the options, so no provider answer can touch it. Clients
still pick it with setOption and read it from the same published fields.

* fix(native-chat): keep a permission mode an earlier dev build saved among options

Hourly builds before the separate field saved the mode in options.permissionMode.
The startup pass now moves it into the field instead of re-deriving it from the
agent's arguments. Temporary; removed after 1.4.226 ships.

* chore: re-run CI against current main

(cherry picked from commit b91795a)
brennanb2025 added a commit that referenced this pull request Oct 15, 2026
…el pick can't reset it (#28268)

* fix(native-chat): picking a Codex model or effort keeps the chat's permission mode

A model or effort pick on a running chat replaced the record's options with
the provider's answer, which names only the provider's own options. The
saved permission mode was dropped, and the next start read it as Ask.

The pick now keeps the record's permission mode beside the provider's
answer; everything the provider reset still resets.

* chore: re-run CI against current main

* chore: re-run CI against current main

* test(native-chat): keep one permission-mode-survives-pick test

* refactor(native-chat): store a chat's permission mode as its own record field

A model or effort pick replaces the record's options with the provider's
answer, which dropped the permission mode saved among them. The mode now
lives beside the options, so no provider answer can touch it. Clients
still pick it with setOption and read it from the same published fields.

* fix(native-chat): keep a permission mode an earlier dev build saved among options

Hourly builds before the separate field saved the mode in options.permissionMode.
The startup pass now moves it into the field instead of re-deriving it from the
agent's arguments. Temporary; removed after 1.4.226 ships.

* chore: re-run CI against current main

(cherry picked from commit b91795a)
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