fix(acp): let an unattended bridge answer session/request_permission - #26
Merged
Conversation
Upstream ad538bf (in the 136-commit ingest, bfa150b) flipped `session/request_permission` from auto-approve to an unconditional `reject_once` and deleted `PermissionMode::BypassPermissions`. That is the right posture for an attended client and the wrong one for our four droplet bridges: nobody is awake to answer the prompt, and @codex publishes its reply *through* `mcp__buzz__buzz_send_message`, so a refused tool call is not an error — it is an invisible turn. All four agents went mute on 2026-08-07 with matched `turn starting` / `turn complete` pairs in the journal. Keep the hardening; add the opt-in upstream is already writing. This is `BUZZ_ACP_PERMISSION_POLICY` from block/buzz PR block#4938, with upstream's exact value names and its `reject` default, so the ingest that carries that PR deletes this patch rather than merging around it. Both hunks say so in a header comment. `allow` selects the request's *unique* `allow_once` option and nothing else: zero candidates, several candidates, or a missing/non-string `optionId` all fall through to the same denial as before. Never `allow_always`, never a hardcoded optionId. Our permission boundary is the OS sandbox — bubblewrap, one UID per identity, `[permissions.*]` in each config.toml, `network.enabled = false` and the broker socket — not an ACP prompt no human is there to answer. Also raise the two permission log lines out of the blind spot that hid this: they log to target `acp::permission`, and `RUST_LOG=buzz_acp=info` filters targets by prefix, so neither ever appeared. The request line goes debug->info and the denial goes info->warn; the unit files gain the matching directive.
|
To use Codex here, create a Codex account and connect to github. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The 2026-08-07 08:46 UTC deploy of
bfa150be5(the 136-commit upstream ingest) took all four server agents mute. Upstreamad538bfb1flippedsession/request_permissionfrom auto-approve to an unconditionalreject_onceand deletedPermissionMode::BypassPermissions.That is the correct posture for an attended client. For our droplet bridges it is a silent outage:
@codexpublishes its reply throughmcp__buzz__buzz_send_message, so a refused tool call is not an error — it is an invisible turn. The journal logged matchedturn starting/turn completepairs, the workers burned CPU, and the channels stayed silent.We are keeping upstream's hardening.
BypassPermissionsis not coming back.What
Adds the opt-in upstream is already writing:
BUZZ_ACP_PERMISSION_POLICYfrom block/buzz PR block#4938 (branchduncan/permission-policy), using upstream's exact value names (allow | ask | reject) and itsrejectdefault. When an ingest carries that PR, this patch is a deletion, not a merge reconciliation — both hunks carry a header comment saying so.allowselects the request's uniqueallow_onceoption and nothing else:allow_oncewith a stringoptionIdallow_oncereject_once)allow_onceallow_oncewith missing/non-stringoptionIdallow_alwayspresentreject(default) oraskaskexists so an upstream-shaped config value can never fail clap parsing and take a bridge down; with no prompt surface it denies likereject.The policy is published once by
Config::from_argsinto a process-wide fail-closedAtomicBool, becausehandle_permission_requestanswers on the agent's I/O task and never sees aConfig. The decision itself is a pure function (permission_response(allow, id, options)), so the tests never touch the global.The boundary
Our permission boundary is the OS sandbox — bubblewrap, one UID per identity,
[permissions.*]in eachconfig.toml,network.enabled = false, and the broker socket — not an ACP prompt no human is there to answer. That sentence is in the patch comment.Logging blind spot
Both permission log lines target
acp::permission, andRUST_LOG=buzz_acp=infofilters targets by prefix — so neither ever appeared, which is why this shipped invisibly. The request line goesdebug!→info!and the denial goesinfo!→warn!. The matchingacp::permission=infodirective lands on the four bridge units in the ERP repo, asserted byprovision-buzz-isolation.sh verify.Test plan
cargo test -p buzz-acp— 722 passed, 0 failed, including:allow_policy_selects_the_unique_allow_onceallow_policy_denies_when_allow_once_is_ambiguousallow_policy_with_no_options_is_cancelledallow_policy_denies_allow_once_without_string_option_idallow_policy_never_selects_allow_alwaysreject_policy_is_identical_to_the_unconditional_denial(numeric id, string id, empty options)only_allow_flips_the_published_policytest_permission_policy_value_enum_is_lowercase,test_summary_includes_permission_policy,test_summary_permission_policy_default_is_reject,test_only_allow_policy_permits_selectiontest_permission_mode_rejects_unattended_bypassis untouched and still passes.cargo clippy -p buzz-acp --all-targetsis clean.Prod round-trip (the check the 08:46 deploy skipped) runs at deploy: a human mentions
@codexin a channel and gets a kind:9 reply, cross-checked against the rollout JSONL under/var/lib/buzz-acp-codex/codex/sessions/where everymcp_tool_call_endmust carry a result rather than{"Err":"user rejected MCP tool call"}.