Chief: your own turns never ask; approvals are opt-in, Stop ends the turn, and an approval is a card
Since 10-20, every Chief turn waits for approval before each local effect. That includes the owner's own cmux chief -p on the same Mac and Chief's own tell and send. Four problems stack on top of that:
remote.autoApprovedoesn't lift the gate.cmux chief stopdoesn't end a turn whose tool call is waiting.- The model re-asks for a denied tool in a loop (turns 146 and 167).
- The approval shows up as a raw JSON bubble.
The CLI was not the cause. cmux chief -p sends as user_local over the owner socket, which is local origin. The cause is e68aa1b4b53: it gates every turn that reads the Home view, and the host reads Home on every turn (host.rs always sets home_view). The bubble blamed "a paired device" only because that text is hard-coded. a4bbe5da6a1 had also made asking the default for paired devices.
What changes
-
Gates are opt-in. Two new per-Chief settings, both off by default:
remote.ask("Ask before acting on requests from other devices")home.ask("Ask before acting on what Home shows")
With both off, the owner's turns from this Mac, from the CLI and from their paired phone run like local turns.
remote.autoApprove: truestill wins overremote.ask. A turn where another participant wrote always asks, which covers the shared-Chief case. The host refuses to loosen a gate during remote-origin work. -
Chief's own tools never wait. That covers
send,reply,react,stop,sleep,profileandspawn. A child spawned in a gated turn still gets the ask floor.tellandcontrolalso run without asking when they carry no prompt. In a gated turn, atell, or acontrolthat carries a message or an answer, still asks unless the target subagent runs withaskitself. That way a gated turn can't pass a command through an approve-all subagent. -
Stop means stop.
chief.stopcancels the session even while a tool call is waiting for approval (Interrupt::request_hard). Every later request in that turn is denied at once. If the model repeats a request that was already denied, it is denied again without a new card; after three repeats the turn is stopped. A newer message no longer denies a pending approval. -
A readable approval card. The approval is now a conversation
questionpart. It shows "Allow Chief to run `rm -rf target`?", a reason line ("Started from Leo's iPhone: ā¦"), the arguments pretty-printed as a monospace preview, and Allow once / Deny buttons. The owner answers withquestion.answer(only the owner's answer counts) or by typingallowordeny. If the owner refuses the part, the same two lines go out as plain text. No JSON appears in the chat.
Who added the gate
Lawrence added the paired-device gate on 2026-10-05 (0654733226a, default ask). He made auto-approve the default on 10-06 (8f1b5b9c65b: "i dont want stuff to require my approval"). On 10-20 his security hardening made asking the default again (a4bbe5da6a1) and gated every turn that reads Home (e68aa1b4b53), because terminal titles, screens and URLs are text an attacker can write. All three went straight to feat-cmux-next; none came through a PR.
Takes back a4bbe5da6a1 and e68aa1b4b53 (both pushed directly on 10-20): together they put an approval in front of every effect of every owner turn, Chief's own messages included. Leo decided on 10-20 that the owner's own devices run like the Mac by default. Both protections stay available as opt-in settings.
Security analysis (risk:high)
- Prompt injection through Home text is now an accepted default risk. With
home.askoff, text in the Home block reaches every turn and runs with the owner's policy, as it did before 10-20.home.askon restores the 10-20 behavior. - A stolen paired phone can act without approval until it is revoked. This matches the 10-06 default.
remote.askon puts approvals back. - Other participants still always ask, and only the owner approves. That holds for both the text answer and the card. A card answer is checked against the respondent the owner stamps, never against the request.
- The newly allowed tools stay inside the conversation or the ask floor:
send,replyandreactpost only to the conversation that asked.sendcan attach local files, but in a non-owner turn the participant already reads that conversation (residual risk).- Children from
spawnget the ask floor. tellandcontrolwith a prompt still ask for approve-all targets.
- No relay command, allowlist entry or socket method is added.
Tests
Command: cmux-ci run --class isolated --script scripts/ci/cmux-tui-rust-check.sh --arg optchat-chief --ref <sha> (optchat-chief tests, then clippy -D warnings, then fmt).
Red commit d5e3840 (new tests only, on base): 8 of the 10 new approval_gates tests fail. The two guard tests (home.ask on, remote.autoApprove wins) already pass on base.
The base also fails four existing tests:
crash::a_pending_turn_and_its_items_survive_a_restart_in_the_databasedaemon_workspaces::a_subagent_workspace_is_located_by_its_public_id_and_sessionturn_seal_binding::a_subagent_cannot_reach_owner_controls_on_the_shared_socketremote_policy::a_non_owner_turn_asks_and_only_the_owner_may_approve_it
Head 30b89d3:
- All of
approval_gates(10) andremote_policy(9) pass. - clippy and fmt pass.
- The same three base reds in crash, daemon_workspaces and turn_seal_binding remain. This PR doesn't touch them.
What tests/approval_gates.rs checks:
- (a) An owner CLI turn with Home runs approve-all.
home.askturns the Home gate on. By default a paired-device turn runs like a local one. - (b)
remote.askgates a device turn, andremote.autoApprovewins over it. The gate can't be loosened during remote work. - (c) Chief's tools are allowed at once. A
tellto an approve-all subagent still asks. - (d) Stop cancels a turn whose call is waiting, and later requests are denied at once. A denied request isn't asked twice, and repeats stop the turn.
- (e) A newer message leaves a pending card alone. The test checks the card's line, reason, arguments and buttons, and that a
question.answerallows the call.
tests/remote_policy.rs now runs with remote.ask on, and its default-case test is flipped.
Observed / Not run
- Observed: the fleet runs above.
- Not run: the Mac app and webview card. Mac Home still draws a
questionpart as its prompt plus "1. Allow once / 2. Deny" text.MessagesLabHome/Cmux/HomeMapping.swiftfalls back to text, andAgentQuestionCardViewisn't wired to the custom-row seam yet. The Claude-desktop-style card with buttons, the āāµ hint and the answered receipt is a follow-up UI PR. This PR only emits the data that card needs. - Not run: a live repro. I didn't reproduce with an isolated
--chief-homeon a nightly build. Leo's Chief stays stopped. - Not run: other brains. The cloud brains (
backend/) keep their own policy, and the shared corpusremote_auto_approvecases are unchanged.
Changelog
- Fixed: Chief no longer asks for approval on your own turns (the
cmux chiefCLI, Home, your paired phone). Asking on requests from other devices or on Home text is now opt-in, Stop ends a turn that is waiting on an approval, and an approval shows as a short card instead of raw JSON.
š¤ Generated with Claude Code
Need help on this PR? Tag @codesmith-bot with what you need.
Summary by cubic
Chief turns no longer wait for approval unless you opt in: your own turns from the CLI, from Home, and from your paired phone run like local turns by default, with remote.ask and home.ask off. chief.stop now ends a turn even while a tool call waits, a denied request isn't asked twice, and approvals appear as a card with Allow once / Deny instead of raw JSON.
Key changes
- Two new per-Chief settings, both off by default:
remote.askgates paired-device turns,home.askgates turns that read Home text;remote.autoApprovestill wins, and another participant's turn always asks. - The Chief's own tools (
sendwithout attachments,reply,react,stop,sleep,profilereads,spawn, andtell/controlwithout a prompt) never wait; in a gated turn,sendwith attachments,profilewrites, andtell/controlwith a prompt still ask unless the target subagent runs withaskitself. chief.stopcancels the session even while a tool call waits, and a request denied once is denied again without a new card; three repeats stop the turn with one line saying so.- Approvals are a
questioncard with a reason line, pretty-printed arguments, and Allow once / Deny; only the owner's answer counts, a newer message never answers a pending card, and a card answered in a side conversation settles the approval posted there. - No gate can be loosened while a turn asks.
- The Mac app and webview render the card as text for now; the button UI is a follow-up.
- Security tradeoff: with gates off, Home text and a stolen paired phone act without approval; the 10-20 hardening is now opt-in.