Skip to content

feat(settings): Agent Permissions offers the same four modes as the chat button - #26117

Closed
brennanb2025 wants to merge 33 commits into
brennanb2025/sta-8261-permission-pickerfrom
brennanb2025/sta-8261-permission-setting-widen
Closed

brennanb2025 wants to merge 33 commits into
brennanb2025/sta-8261-permission-pickerfrom
brennanb2025/sta-8261-permission-setting-widen

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 15 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​698 $\color{#cf222e}{\Huge{\mathbf{−}}}$​95 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​603
Prod 13 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​230 $\color{#cf222e}{\Huge{\mathbf{−}}}$​78 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​152

ELI5

Settings → Agents → Agent Permissions decides how much agents may do without asking. It offered only two choices, Ask or Bypass. The new permission button in the chat composer (#26116) offers four. So a user who wanted every new chat to start in Claude's Auto mode, or Accept edits, had to pick it again in every chat. Terminal tabs couldn't use the middle modes at all.

Agent Permissions now offers the same four options as the chat button, with the same labels: Ask for approval, Accept edits, Approve for me and Full access. Whatever you pick there is what new chats and new agent terminals start with.

What Changed

Before

  • Agent Permissions was Ask or Bypass, globally and for each agent.
  • A user who set defaultMode: auto for Claude got Ask in chat (STA-9517), and Orca had no way to choose Auto by default.
  • New chats could only start in Ask or Full access.

After

  • Agent Permissions and each agent's own override offer the same options as the chat button, with the same icons, descriptions and warning colour for Full access. Each agent lists only what it supports:
    • Claude: all four.
    • Codex: Ask for approval, Approve for me, Full access.
    • Other agents: Ask for approval and Full access, as before.
  • New chats start in the chosen mode. A Claude chat that inherits Accept edits or Approve for me is switched into it before your first message is delivered. If the model has no auto mode, or Claude refuses the mode, the chat starts in Ask for approval instead.
  • New terminal tabs get the matching flag:
Agent Ask for approval Accept edits Approve for me Full access
Claude no flag (unchanged) --permission-mode acceptEdits --permission-mode auto --dangerously-skip-permissions (unchanged)
Codex no flag (unchanged) not offered -a on-request -s workspace-write -c approvals_reviewer=auto_review --dangerously-bypass-approvals-and-sandbox (unchanged)
Other agents unchanged not offered not offered their existing bypass flag (unchanged)

Flags were checked against the installed CLIs' own help (Claude Code 2.1.280, Codex CLI 0.159.0). For Codex, the long form above is what Codex's own --approve-for-me shortcut expands to (checked in Codex's source). It is used instead of the shortcut because older paired Orca hosts recognise -a/-s as a permission choice and won't add their own bypass flag on top.

Mechanism

  • The stored setting now uses the chat's permission type. That is one four-value type, defined once, so the setting and the chat button can't drift.
  • Existing saved values (ask, bypass) keep working with no migration.
  • A value this build doesn't know, for example from a newer Orca, is kept as saved but launches as Ask for approval, never as Full access. An older Orca reading a new value also falls back to Ask.
  • Claude startup now applies an inherited middle mode through the same option path the chat button uses, before the first message is admitted. Previously only a mode the chat had picked itself was applied.
  • Codex states the approval reviewer on both thread start and thread resume, so a resumed chat can't silently keep the reviewer it was saved with. This was already the case; new tests now cover it.

Why

The common pattern has one default-permission setting that offers the same options as the chat's permission control. This makes Orca's single setting complete instead of adding a second one for chat. It also covers the request behind STA-9517 and STA-3680: running in Auto mode, or Codex's automatic review, by default, without reading the CLI's own config files.

Alternatives considered:

  • A separate chat-only default. Rejected: it would be a second copy of the same choice.
  • Following each CLI's own config file (Claude defaultMode, Codex approval_policy). Rejected: chat states the mode explicitly so the button always shows what the chat really runs in. Codex chat already overrides config.toml on purpose (Make the Agent Permissions toggle apply to Codex chat #20977).
  • Codex's --approve-for-me shortcut. Rejected for the mixed-version reason above.

Differences from the common pattern:

  • Intended: agents without a verified middle mode offer only Ask and Full access. If one is ever selected anyway, it falls back to the stricter Ask, never to a looser mode.
  • Intended: a Claude chat that inherits a middle mode starts in Claude's default mode and is switched before the first message, rather than launched in the mode. That way, an unsupported Auto falls back to Ask instead of failing the launch.

Linked Issue

Linear STA-8261 follow-up (also STA-9517, STA-3680). No GitHub issue: maintainer change.

Visual Proof

QA ran in a hidden, isolated Orca built from this branch, with stand-in Claude and Codex processes and never the real CLIs.

Agent Permissions with the four options:

Agent Permissions options

Codex's own override offers only what Codex supports:

Codex override

With the setting on Accept edits, a new Claude chat starts in Accept edits. The stand-in's log shows set_permission_mode acceptEdits arriving before any message:

New chat inherits Accept edits

With the setting on Approve for me, a new Claude chat starts in Approve for me (set_permission_mode auto logged before any message):

New chat inherits Approve for me

Testing

  • 2,712 tests across 29 test files pass, run as explicit files. They cover:

    • saving, loading and future values;
    • each agent's option list;
    • terminal flag mapping on POSIX, PowerShell and cmd, including the stricter fallback;
    • new-chat defaults;
    • Codex reviewer on start and resume.
  • 65 compatibility checks run the previous branch's actual code against the new saved values and launch inputs, including an older host receiving a middle mode.

  • tc:node and tc:web pass. The changed-code quality gate passes.

  • Real Claude CLI (ORCA_REAL_CLAUDE_CLI_TEST=1, both files): 11 passed.

  • Electron QA on macOS. Not covered: the earliest Claude/Codex versions that accept these flags, live Windows/Linux/SSH, and real Codex automatic-review sessions.

  • I manually tested these changes locally

  • Automated tests added/updated

Review

Agent skill upstream boundary

  • Not applicable

Notes

Stacked on #26116 (chat permission button), which is stacked on #24508. Merge order: #24508 → #26116 → this PR.

Checklist

  • I explained what changed and why
  • Before/after screenshots attached (after only for the new options)
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (CI will cover)

…m free-text arguments

Settings stores a per-agent permission mode (a shared default plus per-agent
overrides). One function turns it into the agent's bypass flag at launch, so a
model or other text in Arguments no longer silently turns Yolo off, and Settings
shows each agent's real posture instead of 'mixed' as Yolo.

Refs STA-8902, STA-8699, #23853, #23387
…liases

- Keep the bypass flag inside text that also sets permissions, since a launch adds no flag beside it
- Read codex --yolo and claude --permission-mode bypassPermissions as bypass
- Detect permission options under every launch grammar
- Paired-client writes replace the whole record, as before, and don't flip the mode of text that decides itself
- Show the env editor for env-mode agents; summarize installed agents only; resolve each posture once
- Paired web client keeps the typed shape when merging the host's settings reply
- Detect Gemini/Qwen approval options and Codex attached short options; read Goose's typed env as its posture
- Source Control preview composes args the same way the launch does
- One normalizer for the permission fields; memoize postures; split argument reading into its own module
- Lift the flag from Windows-quoted legacy text
- Paired writes change only the agents they name
- Devin's second bypass option counts as a permission option
- Web client keeps its own agent launch settings when merging a host reply
- Rows with a per-agent permission choice open expanded; narrower memo deps
- Lift the flag after Windows paths POSIX mis-splits (PowerShell grammar fallback)
- Apply the older Devin bypass rewrite in the shared lift (web and paired paths too)
- Cache published launch records per settings snapshot and option names per agent
- Line-specific SAFETY notes on fixture casts in edited test statements
…yolo as bypass

Settings and structured sessions now agree with the launch when these are typed into
Arguments under Manual. Also pin that Codex --search keeps Yolo.
Launch composition and Settings/structured/migration read an agent's
free-text Arguments with two different rules, so some bypass spellings
(Devin --permission-mode bypass, Grok/Cline --opt=value, Codex
-a never -s danger-full-access) launched without prompts while Settings
showed Manual, and Claude --allow-dangerously-skip-permissions stopped
Yolo from adding the flag.

One per-agent table next to the bypass flags and one classifier
(none / bypass / other) now serve launch, Settings, structured Claude
and Codex, the migration and the settings.update lift. It reads
--opt value and --opt=value under POSIX, PowerShell and cmd grammars and
never treats a quoted word as an option. The migration records an agent
in Yolo through an alias as Yolo and keeps its text.

Every load now moves a bypass flag or env found in a typed profile's
text into that agent's mode, so text an older build writes back cannot
latch. Any stored mode marks the profile migrated; one this build does
not know is kept and read as Manual. The per-agent badge is gone; the
summary line, the selected option and the warning already say it.
The classifier skipped quoted words, but the launch strips quotes before
the agent sees argv, so Yolo added a bypass flag beside a quoted Codex
`"-a" on-request` and Settings hid a quoted Claude bypass. It now reads
the unquoted words the CLI receives; only the lift refuses to cut a
quoted span, so stored text stays as written.

Devin's --respect-workspace-trust only skips the folder-trust prompt, so
it no longer decides the permission verdict. It still rides along with
Devin's bypass flag, but composition leaves it out when the user's
Arguments already set it, and the lift keeps the user's own value.

The settings.update write lift and the load lift now share one per-agent
lift: Devin's old short default is rewritten only by the legacy pass, as
main did. An entry an older client writes back unchanged is left alone,
and overrides change only for agents the write changed, so get, write,
get is a fixed point.

Per-agent modes a newer build stored are kept on load and read as ask,
like the shared mode.
New profiles stored agentDefaultArgs as {}, and older builds read a
missing agent entry as "launch with the bypass flag". A paired web
client set to Manual whose host was downgraded got the older web app,
which launched every agent with bypass and Goose with GOOSE_MODE=auto.

Stored profiles, on the host and in the web client, now always hold an
explicit '' for every agent with a bypass flag and {} for env-bypass
agents. One pair of normalizers builds that shape for defaults, the
per-load lift, host and renderer writes and the web merge. A record
missing entries is saved once; clean data still loads unchanged, and
settings.get publishes the same composed values.

Also refreshes two comments left stale by the quoted-option and
unchanged-write changes.
… the card

The Agent Permissions switch cleared every per-agent choice, even when
the selected option was clicked again, so a Codex set to Manual quietly
flipped to Yolo. The switch and onboarding's answer now set only the
shared default; an agent keeps its own choice until its card says
Default. Clicking the selected option writes nothing. The tooltip now
says the switch applies to agents without a setting of their own.

Launches that bring their own arguments (Source Control actions,
fix-checks) replaced the configured Arguments and then took the shared
mode, so an agent whose Arguments ask (Codex -a on-request) got the
bypass flag. The composer now follows the agent's effective mode, the
one its card shows: Arguments that ask keep asking, an alias Yolo
(Gemini -y) still gets the flag, and caller args that set permissions
themselves still decide. The Source Control preview uses the same
composer, so it shows what launches.
The Settings card read an agent's Arguments with the local shell while
launches that bring their own arguments read them with no shell and
ignored grammars that saw nothing, so on macOS Claude
`--model opus ^--dangerously-skip-permissions` showed Manual while a
Source Control launch added the bypass flag. Both now read one posture:
typed text counts as bypass only when POSIX, PowerShell and cmd parsing
all read it so, since the same text can launch under any of them.

"Set separately" now names every agent the switch won't move: each
agent with its own choice, even one equal to the current default or not
installed here (it may run on an SSH host), plus installed agents whose
Arguments or env launch them differently. An agent that isn't installed
but has its own choice shows its Permissions control so Default can
clear it.
The permission reading of typed Arguments changed rule three times, and
each rule broke a different input: requiring every shell grammar to
agree showed a Windows path ending in a backslash before a bypass option
(Gemini --include-directories C:\code\ -y) as Manual while the Windows
launch bypassed, and the load-time lift still cut escaped flags that
the card read as Manual.

Arguments are now read with the shell that will launch them. Settings
and structured Claude/Codex read them with this machine's local launch
shell, resolved as local launches resolve it, and every composer call
passes its launch's own platform and shell, so a local launch always
agrees with the card. A launch on an SSH or paired host reads them with
that host's shell.

The lift that moves the flag out of stored text on migration and every
load cuts only the flag's exact unquoted, unescaped words, so the cut
means the same under every shell; any other spelling stays in the text
and is read by the launch's shell.
The load-time lift found word boundaries with POSIX, then PowerShell.
On macOS a flag between escaped quotes inside a double-quoted prompt is
part of the prompt, but PowerShell splits it out, so every restart cut
it from the prompt and turned that agent's Manual into Yolo.

The lift now cuts the flag only where POSIX, PowerShell and cmd all read
it as a whole, unquoted, unescaped word. If any grammar can't parse the
text or reads the flag inside another word, the text stays whole and
the launch shell reads it, so a wrong call keeps prompting.

The Windows path tests no longer certify a cmd launch with a path that
ends in a backslash: cmd quoting doesn't double that backslash, so the
agent never gets the option after it. That is a separate fix.
Cutting the stored bypass flag trimmed the gap around it, which could
drop a space escaped by a trailing backslash, backtick or caret and glue
two words together: `--add-dir C:\code\  --dangerously-skip-permissions
--model opus` became `--add-dir C:\code\ --model opus`, which POSIX
reads as one word `C:code --model`.

A cut is now kept only if POSIX, PowerShell and cmd each read the cut
text as exactly the original words minus the flag's. Otherwise the text
stays whole and the launch shell reads it, so a wrong call still means
more prompts, never fewer.
Conflict: src/shared/global-settings-types.ts imports (kept both the
permission-fields type and main's ZcodePlanSite type).

Adapted main's new Antigravity reference startup
(ai-vault-antigravity-reference-startup.ts) to the typed permission
mode: it now composes with the full settings and the launch's own
platform and shell, like every other launch path, instead of passing the
raw agentDefaultArgs record.

Updated two tests main added for the typed mode: main's Cursor AI Vault
resume test relied on Cursor's shipped Yolo default, so its fixture now
states Yolo; and a paired-host declined-chat launch now expects the
caller's arguments combined with the client's permission mode, as every
other paired-host caller-args launch in this PR does.
"Set separately: Claude (Manual), Codex (Manual)." read like an
instruction to set those agents manually, and said neither why nor
where to change it.

The line under Agent Permissions now reads "These agents don't follow
this switch:" with one entry per agent: "Claude runs Manual: it has its
own setting.", "Codex runs Manual: its Arguments set -a on-request.",
"Goose runs Manual: its environment sets GOOSE_MODE=approve." Each name
is a link button that opens the agent's row and focuses the control to
change: its Permissions choice, or its Arguments or Environment field
when those decide. It works for agents listed under "Available to
install" too.

That line is now the one place stating each exception: the row's
warning box is gone, and the switch's rule moves from a tooltip into
its description. Adds an `inline` Button size for link buttons inside
a sentence.
An agent that isn't installed here but whose Arguments decide its mode
was listed as "its Arguments set ...", yet its link landed on the
Permissions choice because that row had no Arguments field. The row
under "Available to install" now shows the same Arguments or
Environment field the installed row uses when it decides, and the link
focuses it.

While agent detection is pending or has failed no rows render, so the
names in the line were links that did nothing (and a click while
pending was dropped). They now show as plain text until detection
finishes, then become links.
Clicking an agent's name in the Agent Permissions line with the mouse
focused its Permissions radio, but the radio's ring only draws on
:focus-visible, which a focus after a mouse click doesn't trigger, so
the user couldn't see where they landed. The reveal now focuses with
`focusVisible: true` (supported by the Chromium in Electron 43), using
the existing ring. A normal mouse click on the radio looks the same.
…2025/sta-8902-typed-permission-mode

# Conflicts:
#	src/main/claude/claude-structured-launch-resolution.test.ts
#	src/main/codex/codex-structured-launch-resolution.test.ts
#	src/main/runtime/orca-runtime-get-structured-agent-session-create-support.ts
#	src/main/runtime/orca-runtime-get-worktree-ps.ts
#	src/renderer/src/i18n/locales/es.json
#	src/renderer/src/i18n/locales/zh.json
#	src/renderer/src/lib/ai-vault-resume-command.ts
#	src/shared/global-settings-types.ts
Adds AgentChatPermissionMode ('ask' | 'accept-edits' | 'auto' | 'bypass'), a
superset of the stored Agent Permissions mode, with each agent's offered
subset, the default a setting gives a new chat, and the optional
permissionModes field a host publishes on agentSession.options. An older
host omits the field, which is what keeps a client from showing a picker.

Linear: STA-8261
A new chat records the mode the Agent Permissions setting gives it (create
seeds permissionMode), and every later start prefers the chat's stored mode
over the setting; a chat with none still follows the setting.

Claude: chat modes map to default / acceptEdits / auto / bypassPermissions
and apply live through set_permission_mode. The CLI refuses bypass on a
session launched without --dangerously-skip-permissions, so that pick is
kept as the chat's mode and the delivery path puts the idle child to rest
(the idle sweep's silent stop) before the next send, which starts a child
launched with the flag and resumes the conversation. A child that still
owes work keeps running under its old launch.

Codex: thread/start and thread/resume state the chat's policy (approval,
sandbox, reviewer); a changed mode rides the next turn/start as
approvalPolicy / approvalsReviewer, plus sandboxPolicy only when the
sandbox changes. An app-server that refuses approvalsReviewer is retried
without it, and one that never reports a reviewer offers no Approve for me.

agentSession.options reports permissionModes live and at rest, and the at-rest
write path refuses a mode the agent cannot run.

Linear: STA-8261
…its model

The structured option state holds the chat's mode beside the per-model
record, so a model switch never drops it. It comes from the host's
permissionModes report; before a session exists, a create seed that names a
mode is the host saying it offers the picker. A pick made before the fence
is held by the launch, survives a model pick, and is applied after the
per-model picks. A permission pick is never remembered as the next chat's
launch default: the next chat starts where the setting points.

structuredAgentSessionOptionPicks moves to its own module so the options
module stays within its size limit.

Linear: STA-8261
Its own pill beside the model and options pills, shown only where the host
offers a picker (a terminal-backed chat never does). Ask for approval,
Accept edits (Claude only), Approve for me where the agent supports it, and
Full access in the warning colour on the pill and in the menu. A pick
applies from the next message for Codex and at once for Claude.

Linear: STA-8261
Its own pill beside the options pill, opening a bottom drawer with the same
modes and copy as desktop. Full access reads in the warning colour. Shown
only where the paired host reports the chat's permission modes.

Linear: STA-8261
Move Claude's fast-mode support helper to its own module so the session
options file stays under 300 lines, and read the host's permission report
and a test style through narrowed property access instead of Reflect.get.
@brennanb2025
brennanb2025 force-pushed the brennanb2025/sta-8261-permission-picker branch from 66d4ff4 to da54347 Compare October 7, 2026 21:07
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Closing: the plan changed. Chats now get their own permission setting (Settings → Chat → "Permissions for new chats") in #26116, and the terminal Agent Permissions setting is no longer changed by this work. Widening the terminal setting is not needed.

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