acpmux: Full access approves what the harness still asks (Claude Code's own guards in bypass), logged as "Approved: … (Full access)"
The ask
hb-acpmux asked Leo, through claude-sr2: "[irreversible] should Full access also make acpmux approve the requests Claude Code still raises in bypass (like 'dangerous rm on an unresolvable target')? Yes means those deletions run unasked."
Leo, word for word: "i think it should... idk like if it is on bypass permissions, that's not auto"
claude-sr2's spec: "when a chat is in bypassPermissions / Full access, the harness's own safety guards are approved too, for example Claude Code's "dangerous rm on an unresolvable target" (classifier_approvable:false). Bypass means no prompts; it is not auto mode. Ask-style modes keep prompting. Scope it to the Full access family only (bypassPermissions, agent-full-access and the other entries in full-access-modes.json), and log each auto-approved guard in the chat as a quiet line ("Approved: rm -rf … (Full access)") so it can be seen afterwards. Add a test. Codex xhigh review, then land."
Why it asked
Claude Code 2.1.307 in bypassPermissions still sends can_use_tool for its own guards. cosmic-gray claude-sr2/whats-new-video, event 243: "decision_reason": "Dangerous rm operation on statically-unresolvable target: /private/tmp/claude-501/…/scratchpad/f/*", "classifier_approvable": false, for cd ~/scratch/whats-new-video/reel && rm f/*; ffmpeg …. That chat runs in bypassPermissions (acpmux set it, event 11) with policy null, the daemon default ask, so hub/permissions.rs had no answer (PermissionPolicy::Ask => None) and put a card in front of the person (event 245).
The rule
A chat whose current mode is a Full access mode that cmux set answers each plain tool ask its harness raises with that ask's one-time allow, without asking, whatever the acpmux policy, unless deny-all or a rule decided it. Every such answer is a permission_auto record with fullAccess: <mode>, and the agent pane shows it as one muted line: "Approved: rm -rf … (Full access)".
- Which modes: the shared rule,
spec/full-access-modes.json(start_mode::is_full_access; no new list): Claude CodebypassPermissions, Codexagent-full-access,full-access,yolo, anything with a marker. The mode must be the one cmux set (mode_set_by_cmux, the same checktrust_wait.rsfull_access_by_cmuxuses): the person's pick (throughsession/set_modeor the harness'smodeoption), the start mode, an inherited start, a fork of such a chat. A mode the harness reported by itself (the agent controls that) is not enough, and neither is an argv-pinned--dangerously-skip-permissions. Whilesessions.allowFullAccessis off nothing is approved this way. - Current mode, not the start mode: the mode is read under the permission lock when the request arrives. A chat switched out of Full access mid-turn prompts again from its next request (tested with a FIFO-gated ask).
- Which requests: guards and plain tool approvals alike (bypass means no prompts). Never a question (Claude Code's
AskUserQuestion, a CodexrequestUserInput, anything with_meta.acpmux.questionor aquestionsinput), a plan approval (ExitPlanMode, Grok's and Cursor's plan asks,switch_mode), the folder-trust card, or any request marked interactive: those are for the person and still ask. MCP elicitation forms never reach this path (claude_stdio declines them, the Codex bridge declines every form that is not its own MCP tool approval), so none is auto-answered. - Which option: the request's
allow_onceonly, never an "always" option (it writes a rule). An ask that offers no one-time allow still asks. - What still wins: deny-all (the person's explicit choice) and deny rules deny; an
askrule or anapproverule decides as before; a Web turn is never answered by itself (remote floor). Ask-style modes (default, plan, acceptEdits, auto, Codex's asking modes) are unchanged. - Codex:
agent-full-accessis approvalnever, so Codex raises no command, file or permission approval there; if a future Codex does, it gets the same rule (covered with the stand-in'sfull-accessmode).
What changed
cmux-tui/crates/acpmux/src/hub/permissions.rs:full_access_modeandfull_access_allow;handle_permission_foranswers with them after deny and before the chat allowance and the policy, and recordsfullAccesson thepermission_autorecord (acpmux session tailand_acpmux/eventsshow it). Onetracing::info!per approval.cmux-tui/crates/acpmux/src/hub/permission_groups.rs:plain_tool_ask(shareseligible's interactive checks).hub/web_control.rswrite_mode_state: any write that moves the mode away from the mark takes the mark, under the same meta lock (so a later write cannot bring the mode back with it).hub/turns.rs:set_modemarks the mode only once the chat runs it, and a mode picked through themodeconfig option is marked the same way.hub/fork.rs: a fork keeps its chat's mark;hub/lifecycle.rs: a replayedmodeoption restores the mark as a replayed mode does (its agent gets it back only once it runs that mode,restore_mode_mark).webviews/src/agent-session/acpmux/direct.ts,permissions/record.ts,Localizable.xcstrings: apermission_autowithfullAccessdraws the existing receipt row (.acpmux-permission-record, muted, one line) with the new stringpermission.receipt.fullAccess= "Approved: {action} (Full access)". A policy's or rule'spermission_autostill draws nothing.
Tests (red first on base)
tests/hub_integration/full_access_guards.rs (stand-in Claude Code modes, policy ask):
- Full access + guard → approved with the one-time allow (the lasting one is listed first), logged with
fullAccess, nopermission_request; a plainask:in bypass is approved too. - default / acceptEdits + the same guard → still asks.
- deny rule in Full access → denied (mode stays bypassPermissions); deny-all → denied.
- mode switched out of Full access while the turn waits on a FIFO → the next request asks.
- the harness drifting out of, or into, Full access by itself → asks.
- AskUserQuestion,
_meta.acpmux.question, a lasting-only allow → still ask. - another family's Full access mode (
full-access) → approved; its asking mode asks. - a chat that starts in Full access (start level fullAccess, no pick) → approved.
- a fork of a Full access chat → its first guard approved.
- a harness whose mode is only its
modeoption: Full access picked through the option → approved; the harness moving the option away and back by itself (across turns, or inside one turn) → asks; after an agent restart and on a fork → still approved.
permission_groups.rsunit testonly_a_tool_approval_is_a_plain_tool_ask.
webviews/test/agent-pane-permission-receipt.test.ts: a Full accesspermission_autoleaves "Approved: rm -rf f/* (Full access)"; a policy's leaves none.
Remote fleet (fast-checks):
- Tests only on base (
c5ced87):full_access_guards5 passed, 2 failed (the two "approves" tests; the turn waits on a card). Webviews receipt test: 3 pass, 1 fail ([]instead of["Approved: rm -rf f/* (Full access)"]). - Round-2 tests only (
7bd883d, on the round-1 fix): 8 passed, 2 failed (fork, mode option). - Round-3 tests only (
57e9d3b): 11 passed, 2 failed (restart, fork of a mode-option chat). - Head
9fa0444:cargo fmt --check,cargo clippy -p acpmux --all-targets -D warningsclean;cargo test -p acpmux --no-fail-fast: 94 suites pass; two failures that also fail on baseecd13fb:daemon_chat_opena_long_chat_opens_on_its_newest_page_and_scrolls_to_its_first_message(fails on base every run) andsession_poolpooled_sessions_end_with_the_daemon_and_after_a_crash(flaky: 1 of 3 fails on base, 3 of 3 pass at head). Webviews: receipt test and record.test.ts 7/7; fmt and lint 0 errors.
Capture
The agent pane on a Claude Code chat in Bypass permissions whose harness raised its own guard for rm f/* (the shape of whats-new-video's event 243), a stand-in daemon with the recorded events, captured with Playwright on the fleet (--setup playwright).
| Before (base) | After | |
|---|---|---|
| Dark | ![]() | ![]() |
| Light | ![]() | ![]() |
Before: the turn stops on an Allow/Deny card. After: no card; the work goes on and the transcript keeps one muted line, "Approved: cd reel && rm f/*; ffmpeg … (Full access)", in the existing receipt row (.acpmux-permission-record). One version: the receipt row and its words already exist for a card's answer; this only adds the "(Full access)" wording.
Review
Codex gpt-6-astra xhigh (hb-bypass-guards-review, reading the bare mirror, no checkout).
- Round 1 at
69f963e: BLOCK, 4 findings: a mode-option round trip kept a stale mark; Full access picked through themodeoption was not counted; a fork lost its mark; the stand-in did not run Claude Code's modes. Fixed with tests in7bd883d/20fd856. - Round 2 at
20fd856: BLOCK, 2 findings: the mark was dropped outside the lock that wrote the mode (a concurrent write could bring the mode back with it); a replayedmodeoption did not restore the mark after restart or fork. Fixed with tests in57e9d3b/9fa0444. - Round 3: SHIP, "Reviewed head: 9fa044410e636f130411b41cac30cbcccbcd6cc1".
Takes back #24957: a fork kept no mode_set_by_cmux, so a fork of a Full access chat ran in bypass but asked for every guard (Codex review finding 3). The fork now keeps its chat's mark only when it is the chat's current mode, fresh_agent moves it to replay_mark, and restore_mode_mark gives it back only once the fork's own agent runs that mode; a mode the harness only reported never had a mark to copy, so #24957's rule (only a mode cmux set lends trust) holds.
Stop conditions
No new timer, observer or loop.
Agent session: big-red
🤖 Generated with Claude Code



