Skip to content

buzz-acp: --permission-mode is set but never reaches the agent; agents are silently muted while still billing full turns #5303

Open

Description

Summary

buzz-acp resolves and logs permission_mode=bypassPermissions, but that value is never applied to the claude-agent-acp session. The adapter derives its permission mode solely from the user's ~/.claude/settings.json → permissions.defaultMode. On a machine where that is "auto", every managed agent runs headless in a mode that denies any non-allowlisted tool and cannot prompt — so buzz messages send is refused.

The failure is silent and expensive. Agents wake normally, load their full session, compose a reply, get denied on every send path, and end the turn. The channel stays empty, tokens are billed in full, and nothing is logged as an error at the Buzz layer.

Environment

  • Buzz Desktop 0.5.7 (also reproduced on 0.5.6)
  • @agentclientprotocol/claude-agent-acp 0.64.0
  • Bundled Claude Code CLI 2.1.220
  • Windows 11
  • 12 managed agents (7 claude-agent-acp, 4 codex-acp, 1 native)

Evidence

1. buzz-acp resolves the mode correctly. Every agent's startup line reports it:

buzz-acp starting: ... idle_timeout=900s max_turn=7200s agents=10 subscribe=Mentions
  context_limit=12 max_turns_per_session=0 memory=true permission_mode=bypassPermissions

2. The adapter ignores it. claude-agent-acp dist/acp-agent.js:

const permissionMode = resolvePermissionMode(
    settingsManager.getSettings().permissions?.defaultMode, this.logger);

The session's mode comes from settings.json. The ACP-side permission_mode is not consulted. PERMISSION_MODE_ALIASES maps auto → "auto", so a user with "defaultMode": "auto" gets auto regardless of what Buzz asked for.

3. Result at runtime. Three agents mentioned in one channel message, all denied on every path:

07:13:15  Bash: buzz messages get --channel f5c91c95-...
          → "Permission to use Bash has been denied because Claude Code is
             running in don't ask mode."
07:13:20  PowerShell: buzz messages get ...        → denied
07:13:34  PowerShell: (the composed reply)         → denied
07:13:42  agent: "I'm blocked from replying ... the `buzz` CLI is the only way
                  I can send channel messages."

A second agent independently tried Bash → PowerShell → buzz messages send directly, and was denied all three times.

4. Cost of the silent failure. One halo @AgentA @AgentB @AgentC in a channel:

Agent Requests Input tokens Output Delivered?
A 4 210,565 1,645 no
B 4 188,688 2,284 no
C (codex) 5 116,721 1,367 no
Total 13 515,974 5,296 none

A single DM to one agent cost 601,653 input tokens and also failed to deliver.

5. Timeline. Successful buzz messages send invocations per day, from the agents' own transcripts:

Aug 4: 229    Aug 5: 254    Aug 6: 7    Aug 7: 2    Aug 8: 4 (all denied)

No permission settings changed on this machine across that window (verified against a byte-identical backup: 536 allow rules, defaultMode: auto, zero deny). What changed underneath was the bundled CLI. The adapter carries this comment, which suggests mode semantics moved around then:

// Claude Code 2.1.200 renamed the "default" mode to "Manual" and accepts
// `"defaultMode": "manual"` in settings.json; honor the same alias here.

Why this is worse than a normal permission error

  1. It is invisible in Buzz. The turn completes cleanly. There is no error surfaced in the channel, no delivery failure, and nothing in the agent log to distinguish "had nothing to say" from "was forbidden to speak."
  2. It bills in full. Half a million tokens for one greeting, with zero output delivered. On a fixed subscription this consumes the plan allowance directly.
  3. It scales with the fleet. Every mentioned agent pays independently.
  4. The user's own config is a trap they cannot reasonably discover. permissions.defaultMode: "auto" is a sensible interactive setting. Nothing indicates it silently governs headless managed agents, or that buzz — the agent's only outbound channel — must be present in permissions.allow for agents to speak at all.

Suggested fixes

  1. Pass the resolved permission_mode through to the adapter. claude-agent-acp already reads session options from params._meta — sending it there would make --permission-mode / BUZZ_ACP_PERMISSION_MODE actually effective, and would restore Buzz's control over its own agents rather than deferring to a user setting written for interactive use.
  2. Surface send failures. A buzz messages send that is refused at the permission layer should produce a visible error in the channel or agent status, not a clean exit. This is closely related to [Bug] buzz-acp reply delivery is implicit — Sonnet-class agents answer in session text and every reply is silently dropped #2698 ("tokens are billed, chat stays empty").
  3. Fail loudly at startup. If the resolved agent permission mode would deny the buzz CLI, log a warning at buzz-acp starting: time rather than discovering it mid-turn.

Workaround for anyone hitting this

Add to ~/.claude/settings.json → permissions.allow:

"Bash(buzz:*)",
"PowerShell(buzz:*)"

Narrow, and it restores agent messaging without weakening permissions elsewhere. Setting defaultMode to bypassPermissions also works but applies to the user's own interactive Claude Code sessions on the same machine, which is a much larger blast radius.

Activity

  1. cristiansotogarciaxatech commented on Aug 8, 2026

    @cristiansotogarciaxatech
    Author

    Correction from the reporter — the adapter is not at fault.

    I filed this claiming buzz-acp sets permission_mode=bypassPermissions and that claude-agent-acp discards it. Further digging shows that is wrong, and I want to correct the record rather than leave a misleading report.

    buzz-acp is now explicitly setting dontAsk, and the adapter is honouring it correctly. From the same agent's own startup lines over time:

    2026-08-06T05:54:33  permission_mode=bypassPermissions
    2026-08-06T20:21:47  permission_mode=bypassPermissions
    2026-08-08T06:42:38  permission_mode=dontAsk      <- changed
    2026-08-08T07:40:09  permission_mode=dontAsk
    

    The change lands between 0.5.5 and 0.5.6/0.5.7. 11 of my 12 managed agents now report dontAsk; two still show bypassPermissions. permission_mode does not appear anywhere in managed-agents.json, so this is a default applied outside per-agent config, not something I set.

    So the accurate report is narrower, and it is about the consequence rather than the mechanism:

    Changing the default agent permission mode to dontAsk silently mutes agents whose only outbound channel is the buzz CLI.

    Under dontAsk an agent cannot prompt and cannot run anything outside the user's permissions.allow list. buzz is not in a typical user allowlist — it is the agent's own interface, not something a human would have allowlisted for themselves. The result is that agents wake, load full context, compose a reply, and are denied on every send path, while the turn completes cleanly and bills in full. Measured on my fleet: 515,974 input tokens across three agents for one channel greeting, with nothing delivered, and a separate DM at 601,653 tokens, also undelivered.

    The parts of the original report that still stand:

    1. The failure is invisible. Nothing in the channel, the agent log, or the turn result distinguishes "nothing to say" from "forbidden to speak". Related to [Bug] buzz-acp reply delivery is implicit — Sonnet-class agents answer in session text and every reply is silently dropped #2698.
    2. It bills in full. On a fixed subscription this consumes plan allowance directly, and it scales with every agent mentioned.
    3. A permission-mode change that removes an agent's ability to speak should fail loudly at startup, not mid-turn. buzz-acp starting: already logs the resolved mode; it is well placed to warn when that mode would deny the buzz CLI.

    Suggested, revised:

    • If dontAsk is the intended new default, buzz-acp should ensure its own CLI is permitted under it — either by injecting an allow rule for buzz into the session it creates, or by warning at startup when the resolved mode plus the user's allowlist would deny it.
    • Surface refused sends as an error rather than a clean turn end.

    Workaround, for anyone hitting this. Add to permissions.allow — either user-level, or project-level in the agent workspace so it does not widen your own interactive sessions:

    "Bash(buzz:*)",
    "PowerShell(buzz:*)"

    This restored messaging on existing sessions immediately, without a restart, because allow rules are evaluated per tool call while the mode is fixed at session creation.

    Note that permissions.defaultMode set in a project settings file does not work as an escape hatch — claude-agent-acp's SettingsManager applies filterEscalatingDefaultMode, which strips escalating values from repo-committed sources by design. Broadening permissions.allow at project level is honoured; escalating defaultMode there is not.

    Apologies for the incorrect initial framing.

  2. skillz-xx commented on Aug 8, 2026

    @skillz-xx

    Our same-machine result supports the reporter's corrected framing and the severity assessment.

    On Buzz Desktop 0.5.7, two owner-requested managed Claude review attempts were rejected under the unattended permission surface, including one explicitly read-only request. No Desktop approval card appeared. The same Mac concurrently had an independent Codex adapter network regression; after repairing that separate path, a native buzz messages send returned accepted: true, while managed Claude remained blocked.

    That is useful isolation evidence: the relay, channel, signing identity, and bundled CLI were healthy. The remaining failure was the managed Claude permission path, not delivery infrastructure. The Codex workaround is not a fix for Claude's dontAsk failure.

    The practical impact matches this report: agents look alive and receive the work, but the only publication command is denied after context/model work has already been consumed. In our case, the misleading symptom chain cost almost a working day to isolate.

    I strongly support three acceptance requirements for the fix:

    • current Claude Code in a fresh, untrusted nest;
    • buzz --help succeeds without a manual trust prompt;
    • a signed buzz messages send returns accepted: true, with a visible startup/runtime error if the permission policy would prevent either operation.

    This should be treated as a release-blocking collaboration regression, not merely a permission-mode preference.

  3. talfirevic commented on Aug 8, 2026

    @talfirevic

    Data point that appears to contradict the stated mechanism, from a self-hosted relay.

    Setup: self-hosted relay (compose bundle, ghcr.io/block/buzz:main, revisions 2b873cf then 02f640bc), Buzz Desktop 0.5.6, macOS arm64, claude runtime, built-in personas.

    My machine has exactly the condition described here — ~/.claude/settings.json → permissions.defaultMode: "auto" (plus 104 allow rules) — but BUZZ_ACP_PERMISSION_MODE did change agent behavior.

    Before

    buzz-acp startup line:

    permission_mode=dontAsk respond_to=owner-only model=sonnet mcp_cmd=
    

    The agent's own transcript shows it attempting the only reply path it has, and being refused:

    TOOL:   Bash  buzz messages send --channel <id> --reply-to <id> ...
    RESULT: Permission to use Bash has been denied because Claude Code is
            running in don't ask mode.
    TOOL:   ToolSearch "buzz" → No matching deferred tools found
    TEXT:   "...my Bash tool is coming back denied in this session..."
    

    Harness-side this is completely silent — the turn is a success:

    DEBUG buzz_acp: agent_returned agent=0 outcome="ok"
    DEBUG buzz_acp: dispatch_pending dispatched=0 queue_depth=0
    

    The only events reaching the relay were the harness's own 👀/💬 reactions (kind 7) and their kind 5 deletions. No kind 9. Reproduced identically on two separate mentions.

    After

    Single variable changed: launched the desktop app with BUZZ_ACP_PERMISSION_MODE=default. (The app sets BUZZ_ACP_MCP_COMMAND, BUZZ_PRIVATE_KEY, RUST_LOG etc. explicitly in runtime.rs, but does not set BUZZ_ACP_PERMISSION_MODE, so it is inherited from the app's environment. On macOS a Finder launch inherits nothing, so this needs launchctl setenv to persist.)

    permission_mode=default respond_to=owner-only model=sonnet mcp_cmd=
    ...
    INFO acp::stream: Hey there! 🐝 What are we buzzing on today?
    INFO acp::tool:   tool_call: Terminal (execute)
    INFO acp::tool:   tool_call_update: toolu_01Uhuce… → completed
    DEBUG buzz_acp: agent_returned agent=0 outcome="ok"
    

    Reply landed in the channel as kind 9, ~17s end to end. settings.json was untouched between the two runs.

    Caveat, stated plainly: with 104 allow rules present I can't fully exclude that the buzz invocation matched one. But those rules were identical across both runs, so they cannot by themselves explain the change from denied to completed.

    Why the denial is total silence rather than a degraded reply

    On the claude runtime the shell is the only way an agent can speak:

    • discovery.rs — claude has mcp_command: None (codex has Some("buzz-dev-mcp")), so there is no Buzz MCP send-message tool.
    • acp.rs — agent_message_chunk is logged to acp::stream and returns false; model prose is never published as a channel message.

    So with dontAsk denying Bash, an agent has no path to publish at all. It reacts, thinks, produces a perfectly good answer, and discards it — while billing the full turn, as the title of this issue notes. Related: #5290, #5262, #5260, and #4609 as the likely regression point.

    Two things that made this expensive to diagnose: agent_message_chunk text is logged under target acp::stream, which RUST_LOG=buzz_acp=debug does not match (needs buzz_acp=debug,acp=info), and the turn reports outcome="ok", so nothing in the harness logs, relay logs, or UI indicates a failure occurred.

  4. tonyswartz commented on Aug 8, 2026

    @tonyswartz

    Related, less severe manifestation of the same root problem, for a second data point: an interactive DM session here wasn't muted entirely (its dontAsk allow-list covered basic operation), but still burned real extra tokens working around a permission gap for a single MCP tool lookup unrelated to buzz messages send — checking an already-connected account on an existing connector (Zapier). Two failed tool calls, a drafted-then-discarded settings.local.json patch, and several turns explaining the architecture to the user, before falling back to a separate headless --dangerously-skip-permissions process just to run one read-only lookup. No data loss like the case documented above, but the same underlying gap — the connector's own approval setting never reaches buzz-acp's permission gate — showing up as overhead instead of total denial. Fuller writeup on #2884. Both fixes referenced above (#5325, #5106) should resolve this flavor too once they ship.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions