Skip to content

fix(native-chat): pass saved Claude Arguments to the chat as typed, and refuse what moves the session - #26127

Closed
brennanb2025 wants to merge 4 commits into
brennanb2025/native-chat-codex-argumentsfrom
brennanb2025/native-chat-claude-arguments
Closed

brennanb2025 wants to merge 4 commits into
brennanb2025/native-chat-codex-argumentsfrom
brennanb2025/native-chat-claude-arguments

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

ELI5

Settings → Agents → Arguments is shared by terminal tabs and native chat. In Claude native chat, saved Arguments were only partly applied:

  • some common flags were dropped with no notice: --disallowedTools, --append-system-prompt, --system-prompt;
  • valid flags that take several values or repeat, like --mcp-config a.json b.json, stopped the chat from starting with a message blaming the setting.

Now Claude chat passes saved Arguments to the Claude CLI exactly as a terminal would. It refuses by name only the flags that would move the session somewhere a chat can't follow.

Stacked on #26059 (the same change for Codex). It reuses that PR's refusal wording and unclosed-quote handling. This PR is retargeted to main after #26059 merges.

What Changed

Before (Claude native chat):

  • --disallowedTools "Bash(rm:*)" was dropped, so denied tools were available in chat. The hyphenated spelling --disallowed-tools was forwarded, which was inconsistent.
  • --append-system-prompt "..." and --system-prompt were dropped.
  • --mcp-config a.json b.json, --plugin-dir given twice, and --tools Bash Edit refused the chat with "Saved Arguments give --mcp-config more than one value."
  • A value starting with - (--append-system-prompt "- be terse") was split into a fake flag.
  • Flags that move the session (--worktree, --bg, --cloud, --teleport, --remote-control) were passed through. --worktree would move Claude away from the folder where Orca reads the conversation.
  • Leading prompt text was dropped silently.

After:

  • Every saved Argument is appended, as typed, after the flags the Claude SDK sets, so repeats, multiple values and dash-leading values behave as in a terminal. This includes --append-system-prompt, --system-prompt, --mcp-config, --settings, --plugin-dir, --tools, --add-dir, the allowed/disallowed tool rules (both spellings), and unknown options. The Claude CLI is the authority on its own flags. If it rejects a saved option at start with its "unknown option" error, the chat's error card names that option: "Claude couldn't start. Claude chats can't use --modle from saved Arguments. Remove it from Claude's Arguments in Settings → Agents." Any other start failure from the CLI still shows the generic sentence.
  • The chat's own model and effort pick still wins: when a chat has a saved model or effort, the Arguments' --model / --effort are left out, as before.
  • Permissions come from Agent Permissions (and the per-chat picker landing separately). --permission-mode, --inherit-permission-mode and the bypass flags in Arguments are dropped quietly in chat, the same rule fix(native-chat): apply or refuse saved Codex Arguments instead of silently dropping them #26059 applies to Codex -s / -a. The tool rules --allowedTools / --disallowedTools (both spellings, all values) are forwarded, as decided. Note that --allowedTools lets the listed tools run without asking, exactly as in a terminal.
  • Dropped quietly: flags the chat connection owns (--print, input/output formats, --resume, --continue, --session-id, --fork-session, --verbose, and the like), plus help/version.
  • Refused by name, before anything starts: --worktree / -w (with the hint "A chat already works in its workspace folder"), --bg, --cloud, --teleport, --remote, --remote-control, --tmux, --environment / --pool, prompt text, a known flag missing its value, and an unclosed quote.
  • -m is no longer turned into --model. The Claude CLI has no -m, so chat now fails the same way a terminal does.
  • A saved --thinking-display or --setting-sources now wins over Orca's defaults, as it would in a terminal.

Mechanism: the parser reads saved Arguments with one Claude option table (src/shared/claude-cli-options.ts). It holds every main option of the current CLI with its value arity (switch, required, optional, variadic), so the parser knows which tokens are values. The same table now feeds the terminal-side option remover, so there is no second list to drift. It returns them as ordered token groups. Orca's own Claude process spawner appends them after the SDK's arguments. Before, they went through the SDK's extraArgs, which holds one value per flag. The special --add-dir mapping and the "more than one value" refusal are gone; old hosts can still send the "more than one value" refusal, and clients still read it. configuredArgs is a required field on both launch types, so the type checker guards the whole path from resolver to spawn.

Why

The common pattern parses launch arguments into the SDK's extra arguments. That limits each flag to one value and is the root of the "more than one value" refusals. Appending the user's tokens at Orca's own spawn keeps everything the terminal accepts. The system-prompt flags work from the command line because the SDK leaves the system prompt unset in Orca's setup message, and the CLI only overrides when it is set. A contract test pins this: Orca's setup message carries no system prompt. A live real-CLI case also applies a saved --append-system-prompt whose value starts with a dash.

Deviations from the common pattern:

  • Raw tokens instead of a parsed flag map. Intended: it keeps repeats and multiple values.
  • Session-moving flags are refused instead of passed. Intended: chat must stay in its workspace.
  • Permission mode from Arguments is dropped instead of becoming the session mode. Intended, per the product decision that permissions have one source: the Agent Permissions setting and the per-chat picker.

Linked Issue

Follow-up to #25721 and #26059.

Visual Proof

QA ran on Windows (low spec), driven by a non-Claude agent over the DevTools protocol against a hidden window. A stand-in Claude CLI logged its full command line on every start. The box's real agent config files were unchanged at every step.

Repeats, multiple values and dash-leading values survive. Saved --mcp-config a.json b.json --plugin-dir A --plugin-dir B --append-system-prompt "- be terse" --disallowedTools "Bash(rm:*)" reached the CLI as exactly those 11 tokens, after the SDK's own flags. - be terse stayed one token, and Bash(rm:*) stayed intact through the Windows .cmd shim:
r2-C2.jpg

Permission mode from Arguments is dropped. With Manual plus saved --permission-mode plan --inherit-permission-mode acceptEdits, the CLI got only the SDK's --permission-mode default:
r2-C3.jpg

Session-moving flag refused by name (--worktree feat). Nothing started, and Retry works after editing:
r2-C4.jpg
r2-C8.jpg

A saved option the CLI rejects is named (--modle opus):
r2-C5.jpg

Leading prompt text refused:
r2-C7.jpg

The chat's model pick wins. After picking Haiku, a new chat with saved --model opus got only --model haiku:
r2-C6pick.jpg
r2-C6b.jpg

Testing

  • Targeted vitest files: the launch-args parser, launch resolution, spawn options, the process spawn, refusal copy, and the acquisition fixtures.
  • tc:node, tc:web and tc:cli typechecks.
  • oxlint and the changed-code quality gate.
  • Real Claude CLI suite (claude-structured-real-cli*.test.ts, run with the real CLI on the final head 70764b0): 13 passed, 0 skipped, including the new saved --append-system-prompt case.
  • Electron QA on Windows (low spec) against a stand-in CLI: see Visual Proof.

Review

Pending.

Agent skill upstream boundary

  • Not applicable, or this change follows docs/reference/agent-skill-sharing-upstream-boundary.md and copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

  • The Claude model-list probe still ignores Arguments. The picker shows the running chat's model once it starts.
  • SSH/WSL: unchanged. Arguments are read where the chat runs.
  • Mixed versions: no new wire kinds. An older host's "more than one value" refusal still renders.
  • Deferred: a stray second value after a single-value flag (--plugin-dir /a /b) is refused as "Saved Arguments include a prompt." Refusing is right, because the CLI would take /b as a prompt, but the wording could be better.

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)

…nd refuse what moves the session

Settings -> Agents -> Arguments is shared by terminal tabs and Claude native
chat. The chat squeezed the Arguments into the SDK's one-value-per-option
record, so a valid `--mcp-config a.json b.json`, a repeated `--plugin-dir`,
or `--tools Bash Edit` refused to start the chat, and a dash-leading value
(`--append-system-prompt "- be terse"`) was split. It also silently dropped
`--allowedTools`/`--disallowedTools` (one spelling only), the system-prompt
options and `--setting-sources`, dropped a prompt typed before the options,
and forwarded `--worktree`/`--bg`/`--cloud`, which move the session away
from the folder the chat reads.

Now every option the chat doesn't own reaches the Claude CLI exactly as
typed, after the SDK's own flags, with all its values and repeats; values
are read with the CLI's own arity rules. The system-prompt options apply as
in a terminal (Orca's initialize sends no prompt of its own). The chat
still owns the stream protocol, the session's identity and the permission
bypass, which drop quietly; `--permission-mode` drops and tool rules pass,
behind one switch pending a product decision. Options that move the session
(--worktree, --tmux, --bg, --cloud, --teleport, --remote, --remote-control)
and prompts refuse by name; an option the CLI doesn't list is forwarded and
left to the CLI. A chat's saved model or effort still replaces the
Arguments' own.
…d every CLI option by one table

A misspelled or unknown option in saved Arguments (--modle, -x) reached
the Claude CLI, which exits with "unknown option '<name>'"; the chat only
said Claude stopped before it finished starting. When the CLI's exit
carries that exact refusal for an option the user saved, the start now
fails as the existing named refusal: "Claude chats can't use --modle from
saved Arguments. Remove it from Claude's Arguments in Settings -> Agents."

Option arity now comes from one table of every main-command option of the
Claude CLI (src/shared/claude-cli-options.ts), which the shared option
remover also derives its required-value list from, so hidden options keep
their dash-leading values and prompt text after them is still refused.
--environment and --pool (cloud sessions) refuse by name, and
--inherit-permission-mode drops like --permission-mode: the chat's mode is
Agent Permissions'. Tool allow/deny rules are forwarded, each behind one
switch.

The launch's saved Arguments are required end to end, the SDK contract pin
asserts Orca's initialize sends no system prompt of its own (so a saved one
stands), and multipleValues is marked as read for older hosts only.
…rguments' into brennanb2025/native-chat-claude-arguments
@brennanb2025

brennanb2025 commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor Author

Review summary

Problem. In Claude native chat, saved Arguments (Settings → Agents → Arguments, shared with terminal tabs) were only partly applied:

  • --disallowedTools (camelCase), --append-system-prompt and --system-prompt were dropped with no notice;
  • valid repeated or multi-value flags (--mcp-config a.json b.json, --plugin-dir twice) refused the chat with a message blaming the setting;
  • values starting with - were split;
  • session-moving flags like --worktree were passed straight to the CLI.

User-facing change.

  • Saved Arguments reach the Claude CLI exactly as typed, after the SDK's flags.
  • Orca keeps only the connection, session identity and permissions:
    • --permission-mode, --inherit-permission-mode and the bypass flags are dropped, per Brennan: chat honors the Agent Permissions setting and the per-chat picker, not a typed mode;
    • tool rules are forwarded in both spellings.
  • Session-moving flags and prompt text are refused by name.
  • A saved option the CLI rejects is named on the card.
  • The chat's model and effort pick still wins over --model / --effort.

What review fixed (2 rounds plus the earlier design reviews):

  • One shared Claude option table: all 125 main options of CLI 2.1.280 with their value arity. The terminal-side remover reuses it, so the two can't drift.
  • A misspelled saved option is now named instead of "stopped before it finished starting".
  • --inherit-permission-mode is dropped like --permission-mode.
  • --environment / --pool are refused by name.
  • configuredArgs is required along the whole launch path, so no builder can silently lose Arguments.
  • A deterministic contract test pins that Orca's setup message sends no system prompt, which is what keeps saved system-prompt flags in force.

Deferred or follow-ups:

  • Already on main, not changed here: two failure surfaces. Arguments refused before Claude starts show a composer notice with Retry and mark the chat Failed. An option the CLI rejects after it starts lands in the transcript, with "Send your message to try again.", no Retry and no Failed mark, because the create already succeeded. This PR only changes that card's wording.
  • Seen in QA, not checked on main: a stale failed chat is reused. Opening a new Claude chat can land on an earlier empty, failed chat, and its old failure card stays above the working conversation. Repro: save --modle opus, open a new Claude chat (card shown), fix Arguments, open "new" Claude chat again; it lands on the same tab and keeps the stale card.
  • A stray second value (--plugin-dir /a /b) is refused as "Saved Arguments include a prompt."
  • Minor naming nits: --bare=x is named --bare, and -xyz is named -x.
  • The Claude model-list probe ignores Arguments.

Verified:

  • Typechecks tc:node, tc:web and tc:cli; 135 targeted test files (1616 tests).
  • Real Claude CLI suite on the final head 70764b0: 13 passed, 0 skipped. This includes a saved --append-system-prompt whose value starts with a dash reaching the live session.
  • Full CI on 70764b0: unit full, LoC and Mobile pass; Headless skipped.
  • Electron QA on Windows (low spec) with a stand-in CLI: C1–C8 all pass. That covers the exact command-line tail, permission flags dropped, the refusals word for word with nothing started, the named CLI rejection, the model pick winning, and Retry. The box's real configs were unchanged.
  • Arity and system-prompt behavior read statically from SDK 0.3.284 and CLI 2.1.261/2.1.280.

Not verified:

  • Claude CLIs older than 2.1.261.
  • macOS/Linux Electron QA (Windows only).
  • SSH (Claude native chat Arguments are read where the chat runs; unchanged).
  • Restart of the same chat with a saved pick (covered by a new chat instead).

Stacking: this targets #26059's branch and will be retargeted to main after #26059 merges (merge order #26116 → #26059 → this).

@brennanb2025

Copy link
Copy Markdown
Contributor Author

Closing unmerged. Native chat no longer reads saved Arguments for any agent; they are for terminal launches (#26506). So passing Claude Arguments through to chat isn't needed. Claude chat settings come from Claude's own settings files.

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