Repository navigation
acpmux: a narrower policy holds on presets, resume, asides, handoffs and a deny-all default - #23639
Conversation
…ux's own policies or a deny-all default Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d3d-4bad-7860-b096-c54282b4bec4&host=cosmic-gray
…olicy changes A chat's narrower policy was put in a mode that asks only on a plain session/new and _acpmux/set_policy. It now also holds: - at every agent start (preset, folder profile, pinned profile, handoff target keeping its harness's mode, resume, fork); - on every policy acpmux sets itself (an aside's deny-all, a handoff target narrowed to its source), since Hub::set_policy follows the policy; - for deny-all as the default policy, which every chat without its own policy follows; - against a switch into a mode that does not ask (refused, policy_mode); - in the _acpmux/start_mode preview (source policy). A chat being closed no longer counts as running because its host still holds its record. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d3d-4bad-7860-b096-c54282b4bec4&host=cosmic-gray
|
cmux status (one comment, edited in place; each line is updated by the job that owns it) Review App of this head (app.zip, kept 1 day)
red: cmux-next checks (god files, concurrency, crash safety, l10n); base red: the same failure on the base c8bfa7b; does not block |
|
Owned by attachments-matrix on cosmic-gray · session |
|
Bead |
|
Found 9 test failures on Blacksmith runners: Failures
|
…er a narrower policy fake_claude now reports the mode it was switched to in its next init, as Claude Code does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d3d-4bad-7860-b096-c54282b4bec4&host=cosmic-gray
…ig default and preset start modes Review follow-ups: - A Claude Code start returned before the policy's mode was held. It now spawns in the policy's mode, and a mode pinned in argv is moved after the start. - deny-all written in config.json is followed at daemon start. - A closed chat whose host is reached again is no longer marked closing. - set_default_policy follows every chat at once, skipping chats being moved. - A preset start mode that would not ask is skipped, not fatal. - The refusal counts the configured asking modes (webAskingModes). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d3d-4bad-7860-b096-c54282b4bec4&host=cosmic-gray
Bugbot needs on-demand usage enabledBugbot uses usage-based billing for this team and requires on-demand usage to be enabled. A team admin can enable on-demand usage in the Cursor dashboard. |
|
|
…r a deny-all default Also pins the config.json deny-all lockdown at startup. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d3d-4bad-7860-b096-c54282b4bec4&host=cosmic-gray
A deny-all default skips a moving chat, and nothing followed it after an aborted move, so it kept running in full access. The abort now follows the chat's policy before its queue runs again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d3d-4bad-7860-b096-c54282b4bec4&host=cosmic-gray
Bugbot needs on-demand usage enabledBugbot uses usage-based billing for this team and requires on-demand usage to be enabled. A team admin can enable on-demand usage in the Cursor dashboard. |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d3d-4bad-7860-b096-c54282b4bec4&host=cosmic-gray
…n abort does Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d3d-4bad-7860-b096-c54282b4bec4&host=cosmic-gray
|
Review (subagent, correctness first): SHIP at bdc81bd Earlier rounds found holes and each was fixed: the Claude start path, a deny-all default in config.json at startup, readopt, preset start modes, a move that aborts, and a move whose begin fails. This last review checked every write to Not tested, and disclosed in the body:
Base reds, not caused by this PR:
|
…pane prompt Red at this commit: set_restricted holds spawn_lock while set_policy's follow_policy (from base #23639) takes it again, so the prompt hangs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d41-f5e8-7631-b4aa-a544e3ef96a8&host=cosmic-gray
set_policy now moves a mode that does not ask under spawn_lock (follow_policy, #23639); set_restricted held that lock and the turn lock across the call, so a chat that had run in full access hung at the prompt that restricted it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d41-f5e8-7631-b4aa-a544e3ef96a8&host=cosmic-gray
|
Shipped in:
|
…it drive a remote chat in Full access (#25038) * acpmux: failing tests, a paired machine cannot be let run chats in Full access The two remote_guard_codex_bridge tests red on base since #23572/#23639 become two states: with a paired machine's Full-access setting off (and on the shared dashboard token or a peer token, which name no machine) a chat the Web started stays in a mode that asks; with it on, that machine's next request drives the Codex bridge in every mode. New: a machine without the setting stays refused while one with it is allowed; turning it off or revoking the machine counts for its next request; only the person turns it on, for a client machine. Scaffolding: the setting on the device record, _acpmux/pair_set, the CLI, and the device id on the connection. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16c8c-54ec-71b2-a0fa-125d1404dd72&host=big-red * acpmux: a paired machine the person lets run chats in Full access moves a remote chat out of ask Leo: "#2 seems reasonable i think... it's a gate that you can enable for your phone, which is an attack vector you opt in to". A chat a remote connection starts has the policy ask, which since #23572 holds it in a mode that asks, so no Web client could put the Codex bridge in agent-full-access. A client machine whose Full-access setting is on (_acpmux/pair_set, on only from the person) may now do that for a chat a remote connection started, for its own requests held to the Web's rules. Deny-all still holds, and a chat started on this machine keeps its policy's mode. The setting is read on every request; turning it off or revoking the machine puts every such chat back in its policy's mode and stops its turn before the Mac's request is answered, and a switch that raced with that is put back too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16c8c-54ec-71b2-a0fa-125d1404dd72&host=big-red * acpmux: a chat in Full access by a machine's setting is driven only by a request it covers A prompt, a steer or a queued prompt's dispatch on a chat a remote connection started whose mode its policy no longer holds (only the Full-access setting moves one there) needs the request's own machine to have the setting now: the shared dashboard token and other machines are refused (remote.full_access_device). An edit of a queued prompt keeps its machine only when the same machine edits it; a restored prompt names none. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16c8c-54ec-71b2-a0fa-125d1404dd72&host=big-red * acpmux: rustfmt, and the Full-access hold reads only the chat's record Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16c8c-54ec-71b2-a0fa-125d1404dd72&host=big-red * acpmux: review: only the granted machine changes a Full-access chat's turn and queue; a revoke stops a Web turn before its prompt - the remote guard holds cancel, queue edits and removes, and permission answers on a chat in Full access by a machine's setting to that machine, and queue_edit checks again before it changes the queue; - turning the setting off marks a running Web turn cancelled before the chat goes back to its policy's mode, and the turn checks the setting again just before its prompt goes out; - a queued prompt sent now as a steer, and a dispatch's prompt, keep the machine that sent them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16c8c-54ec-71b2-a0fa-125d1404dd72&host=big-red * acpmux: rustfmt the review test Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16c8c-54ec-71b2-a0fa-125d1404dd72&host=big-red * acpmux: review: a restart puts chats moved by a Full-access setting back; queue_remove checks again before it removes - at daemon start, after the agent hosts are adopted, every chat a machine's setting moved out of its policy's mode goes back (which machine moved it is not kept, so it fails closed), and an adopted Web turn in such a mode is floor-cancelled; - _acpmux/queue_remove checks the hold again after its log wait, right before it removes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16c8c-54ec-71b2-a0fa-125d1404dd72&host=big-red --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
![Fix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/fix-with-codesmith-light-3x.png)
Summary
#23572 put a chat with a narrower permission policy (ask, approve-reads, approve-edits, deny-all) in a mode that asks, but only on a plain
session/newand on_acpmux/set_policy. Several other paths could still leave the chat in a mode that never asks, so the policy held nothing there. This PR closes those paths, at four points:hold_policy_mode, run aftercap_full_accesson a cold or pooled start, and on a Claude Code start, which spawns in the policy's mode when its saved mode doesn't ask). This covers:approve-readsran inagent);Hub::set_policynow follows the policy, the same way_acpmux/set_policyalready did.defaulton Claude._acpmux/set_default_policy deny-all, orpermissionPolicy: deny-allin config.json at startup).askdoes not narrow anything. Chats still start in full access by design (Lawrence 2026-10-14).data.reason: "policy_mode"while the chat's harness has a mode that keeps the policy. This coverssession/set_modeand themodeconfig option, from any client. A harness with no such mode (OpenCode, pi) is left as before; it logspolicy_unenforced.Also:
_acpmux/start_modepreview answers the policy's mode withsource: "policy", for a preset's policy or a deny-all default.set_policyrace from the acpmux: a narrower permission policy starts Codex and Claude in a mode that asks #23572 review is closed:killmarks the chatclosinguntil its agent starts again (or is readopted).set_policyin that window saves the mode instead of starting the chat again.killthat lands betweenfollow_policy's running check and itsset_modestill starts the chat again. Any client'sset_modehas the same window today.plan, a Codex read-only sandbox) becomes the policy's mode, which also asks.Changelog
Fixed: a chat with a narrower permission policy now stays in a mode that asks when it is started from a preset, resumed, forked as an aside, handed off, or locked down by a deny-all default policy.
No capture: daemon-only change with no UI.
Testing
Focused command:
cmux-tui.ymldispatch,mode=focused.policy:a_preset_chat_with_a_narrower_policy_starts_in_the_mode_that_keeps_itfails attests/start_mode.rs:643: the Codex preset chat with approve-reads runs inagent;a_policy_acpmux_sets_itself_moves_the_chat_out_of_full_accessfails at:659: afterHub::set_policy(deny-all)the chat stays inagent-full-access;deny_all_as_the_default_policy_holds_on_every_chatfails at:678: afterset_default_policy deny-allthe live chat stays inagent-full-access.policytest passes.policy: 108 passed, 0 failed, the three new tests included;aside9/0, run 39652512044handoff45/0, run 39652517111btw13/0;_mode: 34 passed, 1 failed. The failure istransfer_guard::a_chat_with_no_asking_mode_moves_under_peer_control_but_not_under_web, a base red.pinned_claude:a_pinned_claude_chat_with_a_narrower_policy_leaves_full_accessfails attests/start_mode.rs:722. A Claude Code harness that pins--permission-mode bypassPermissionskept it under approve-reads.policy: 109 passed, 0 failed;claude: 168 passed, 0 failed, the new test included;_mode: 34 passed, 1 failed, the same base redtransfer_guard::a_chat_with_no_asking_mode_moves_under_peer_control_but_not_under_web.start_gate, selected no tests.)deny_all:a_chat_whose_move_aborts_follows_a_deny_all_default_set_meanwhilefails attests/start_mode.rs:762. After the move aborts, the chat is still inagent-full-access.deny_all: 12 passed, 0 failed. That includes the move-abort test anddeny_all_in_config_holds_on_every_chat_from_startup(config.json deny-all, thenfinish_startup).policy, 109/0. Runs 39662112199 (transfer, 22/1) and 39662120630 (move, 43/1) fail only the base redtransfer_guard::a_chat_with_no_asking_mode_moves_under_peer_control_but_not_under_web.end_moveas an abort): run 39666228156, filterdeny_all, 12/0; run 39666231887, filterpolicy, 109/0. Run 39666222956, filtertransfer, 22/1: only the base redtransfer_guard::a_chat_with_no_asking_mode_moves_under_peer_control_but_not_under_webfails.lint (linux)is red only on the base clippy redundant clones.Observed / Not run
bypassPermissionsunder approve-reads runs indefault.read-only. The stopped chat staysread-onlyafter its next prompt starts it again, and a new chat starts there.read-onlywith sourcepolicy.session/set_modetoagent-full-accessis refused withpolicy_mode.ask_asideandhandoff_prepareend to end. Both now go throughHub::set_policy, which the test calls directly.start_gate(the preset test covers the cold-start hold, not the start-mode refusal), readopt clearingclosing, and an unpinned Claude chat resumed frombypassPermissions.transfer_beginfails (a turn that doesn't stop within 20 s, a bundle error) while deny-all becomes the default. It goes through the sameend_moveas an abort, which the move-abort test covers.closingrace on a hosted chat. I couldn't make the window between a kill and its host's exit deterministic in a test; the fix follows from the code._acpmux/start_modepreview for a web Codex chat (a remote chat answers no preview).Agent session: cosmic-gray
🤖 Generated with Claude Code