Permission extension
September 17, 2026 ยท View on GitHub
This document defines the experimental permission presentation extension implemented by
claude-agent-acp. The adapter uses standard ACP permission requests and responses, while _meta
adds compact request presentation text.
The extension does not replace ACP permissions. RequestPermissionRequest.toolCall, options,
RequestPermissionResponse.outcome, and each option's standard kind remain authoritative. A client
that does not understand the extension can ignore _meta and render the ordinary ACP request.
Request lifecycle
When the Claude Agent SDK invokes canUseTool, the adapter:
- stops immediately if the tool-call signal is already aborted;
- ensures the referenced ACP
tool_callhas been emitted; - validates and snapshots any SDK
suggestions: PermissionUpdate[]; - builds a tool-specific presentation and ordered option list;
- sends
session/request_permissionwith the tool-call cancellation signal; - validates that the selected option was offered in the exact request;
- maps the selection to a Claude SDK
PermissionResult.
The eager tool_call announcement always precedes the permission request. If the announcement fails,
its de-duplication marker is removed so the normal streamed tool-use path may retry it.
Request-level presentation metadata requires no capability negotiation. It is an additive _meta
record and may be ignored by clients that do not render it.
Request presentation
The request-level record is placed under RequestPermissionRequest._meta.permission:
{
"sessionId": "session-1",
"toolCall": {
"toolCallId": "toolu_1",
"name": "Bash",
"status": "pending",
"rawInput": { "command": "npm test" }
},
"options": [
{ "optionId": "allow-once", "name": "Yes", "kind": "allow_once" },
{ "optionId": "reject", "name": "No", "kind": "reject_once" }
],
"_meta": {
"permission": {
"version": 1,
"title": "npm test",
"description": "Reason: Needed to verify the change."
}
}
}
| Field | Required | Type | Meaning |
|---|---|---|---|
version | yes | integer 1 | Permission presentation schema version. |
title | yes | non-empty string | The standard tool-call operation title. |
description | no | string | Temporary diagnostic SDK decision reason. |
defaultToNo | no | true | The ask must not be approvable by a stray keystroke; the reject options are listed first and a client that pre-selects an option should focus the decline. |
The permission title normally duplicates toolCall.title: one operation has one heading across the
tool card and approval UI. ExitPlanMode is the deliberate exception and uses the action-oriented
permission heading Ready to code?. Commands, paths, URLs, and other structured details remain in
rawInput, content, and locations.
description temporarily carries the SDK's non-blank decisionReason for diagnostics, prefixed
with Reason: . The SDK description operation subtitle is not copied there.
Tool-call presentation
The permission request carries the same standard ACP tool information used for normal tool updates:
nameis the SDK tool name (for exampleReadormcp__server__tool), matching the standardnamefield on the initialtool_callupdate;kind,title,content, andlocationscome fromtoolInfoFromToolUse;statusispending;rawInputis the original SDK input object;blockedPathis appended tolocationswhen it is valid and not already present;- subagent calls include
_meta.claudeCode.parentToolUseIdon the tool call; - Sandbox Network and Computer Use requests synthesize JSON content when the normal renderer has no content.
Compact text removes control characters, collapses whitespace where appropriate, and enforces length
limits. Invalid optional presentation text is omitted instead of being truncated into misleading UI.
Shell permission titles (Bash and PowerShell) preserve the full command verbatim, including
whitespace and line breaks, without applying compact-text normalization or length limits.
Permission options are fixed. When a durable suggestion contains a command prefix, path, host, or
other rule, the adapter includes that value directly in PermissionOption.name, for example
Yes, and don't ask again for npm test commands. Selecting the option applies the exact snapshotted
Claude SDK PermissionUpdate; the client cannot edit it. A selected reject is distinct from
{ "outcome": "cancelled" }: cancellation aborts the tool use instead of returning a user rejection.
Standard option ids
| Option id | Meaning |
|---|---|
allow-once | Allow this call without changing permission state. |
allow-with-updates | Allow and apply the displayed durable effect. |
allow-skill-exact | Allow the exact Skill invocation in local settings. |
allow-skill-prefix | Allow the parameterized Skill prefix in local settings. |
exit-plan-auto | Exit plan mode and set session mode to auto. |
exit-plan-bypass | Exit plan mode and set session mode to bypassPermissions. |
exit-plan-accept-edits | Exit plan mode and set session mode to acceptEdits. |
exit-plan-default | Exit plan mode and set session mode to default. |
exit-plan-clear-auto | Clear context, continue the plan, and use auto. |
exit-plan-clear-bypass | Clear context, continue the plan, and bypass permissions. |
exit-plan-clear-accept-edits | Clear context, continue the plan, and accept edits. |
reject | Deny the call, optionally returning feedback to Claude. |
The adapter stably groups options as allow_once, allow_always, then reject kinds. Options of the
same kind retain their builder order. Clients must return the selected option id unchanged.
Durable SDK suggestions
Claude may provide suggestions: PermissionUpdate[]. Before a suggestion can appear as a durable
choice, the adapter validates the complete array and takes a structured clone. Validation recognizes:
addRules,replaceRules, andremoveRules;setModefor known Claude permission modes;addDirectoriesandremoveDirectories;- known destinations and rule behaviors;
- bounded array sizes and bounded non-empty strings.
Unknown update types, invalid values, oversized bundles, non-cloneable data, and empty suggestion arrays are omitted. The snapshot prevents the label shown to the user from diverging from the effect returned after a delayed response.
An SDK suggestion is offered only when the tool-specific builder can accurately describe the entire effect. The adapter never labels one part of a mixed bundle while silently applying the rest.
When matchedAskRule is present, all persistent choices are suppressed. A configured ask rule must
continue asking; accepting a generated allow rule that cannot override it would be misleading.
Tool-specific behavior
| Tool family | Permission choices and effects |
|---|---|
| Bash / PowerShell | One-time allow; exact representable SDK bundle with its complete effect in the option label; reject. |
| Read / Glob / Grep | One-time allow; matching session-scoped read grant; reject. |
| Edit / Write / NotebookEdit | One-time allow; matching session edit grant, including .claude handling; reject. |
| WebFetch | One-time allow; generated domain:<hostname> local rule; reject. |
| Skill | One-time allow; exact skill and optional prefix:* local rules; reject. |
| EnterPlanMode | Enter plan mode once, or reject and continue implementing. |
| ExitPlanMode | One keep-context and one clear-context elevated mode (auto > bypass > accept edits), manual approval, or reject. |
| SandboxNetworkAccess | One-time allow; exact SDK host rule when representable; reject. |
| Computer Use MCP | One-time allow; exact representable SDK allow suggestion; reject. |
| MCP | One-time allow; exact representable SDK allow suggestion for that tool; reject. |
| WebSearch / Agent / Task | One-time allow; generated whole-tool local rule; reject. |
| ReviewArtifact / Workflow | Generic fallback behavior until SDK renderer state is available. |
| Monitor / unknown tools | Generic fallback behavior. |
Generated WebFetch, Skill, and non-MCP fallback effects use localSettings. Filesystem grants are
offered only when the SDK bundle is session-scoped and covers the current path. Shell labels may
describe command rules, read paths, additional directories, or a representable combination.
AskUserQuestion is not a permission dialog. With ACP form elicitation support it is routed through
ACP elicitation; without that support it is disabled when the Claude session is created.
Permission modes and bypass
The session exposes the Claude modes available for the current model using Claude Code's labels:
Manual, Accept edits, Plan, conditional Auto, and conditional Bypass permissions. The
internal dontAsk SDK mode is accepted from settings for compatibility but is not advertised as a
user-selectable mode.
Hosts can remove Bypass permissions from a session by sending
_meta.claudeCode.options.allowDangerouslySkipPermissions: false on session/new (and again on
session/load); the mode leaves the catalog and a bypassPermissions settings default clamps to
default.
The ACP mode ids are the Claude SDK wire ids: default, acceptEdits, plan, auto, and
bypassPermissions. Manual deliberately retains the SDK id default; manual is only a settings
input alias. The adapter does not invent parallel ids for presentation labels.
Like Claude Code, ExitPlanMode offers one elevated keep-context choice by priority: auto, then
bypassPermissions, then acceptEdits. A non-empty plan also gets exactly one clear-context choice
with the same priority. This requires no ACP capability or setting: after selection the adapter
interrupts the old private Claude query, hides that internal rejection from the client, creates a
new private query under the same public ACP session, and continues the pending turn with
Implement the following plan: followed by the complete plan. Mode, model, agent, effort, Fast
mode, and accumulated turn usage are preserved.
The SDK cannot reapply allowedPrompts through a public control request, so those renderer hints do
not survive this internal handoff. Ultraplan remains unavailable because its required renderer state
is not exposed by the SDK.
The adapter does not auto-allow a callback merely because the session advertises
bypassPermissions. Claude Code applies bypass before invoking canUseTool. A request that still
reaches the callback is bypass-immune, safety-sensitive, interactive, or forced by an explicit ask
rule and must remain visible to the user.
Cancellation and failures
The SDK tool-call AbortSignal is forwarded to the ACP client. The adapter also races the client
request locally against the same signal, so a client that ignores cancellation cannot leave Claude's
tool call waiting forever.
These cases all abort the tool use with Tool use aborted:
- the signal was already aborted;
- the signal aborts while the permission request is open;
- the client returns a cancelled outcome;
- the client rejects after cancellation.
A normal selected reject returns behavior: "deny", decisionClassification: "user_reject", and
the default refusal message.
Client requirements
A client implementing version 1 should:
- render standard ACP tool-call content and locations as the action subject;
- treat request
_meta.permission.titleanddescriptionas optional presentation hints; - preserve option ids;
- ignore unknown
_metafields and future feature names; - settle the request when its cancellation signal fires.
The client must not infer storage scope, lifetime, or permission effects from kind or button text.
The adapter owns the Claude PermissionUpdate and applies it only after validating the response.
Current limits
The SDK does not expose enough renderer state to reproduce every Claude Code dialog. Version 1 does not implement:
- Bash output-redirection rewriting;
- classifier-specific prompt state;
- ExitPlanMode Ultraplan actions;
- Computer Use macOS/TCC or application-allowlist dialogs;
- structured permission lifetime or storage metadata for clients.
These omissions do not weaken the one-time choice. When a durable effect cannot be represented honestly, the adapter sends only one-time allow and reject.
Verification
The executable contract lives in:
src/tests/permission-presentation.test.tsfor capability and request presentation;src/tests/permission-options.test.tsfor tool-specific option construction;src/tests/permission-effects.test.tsfor response validation and effects;src/tests/acp-agent.test.tsfor request ordering, cancellation, bypass, and subagent attribution;src/tests/session-config-options.test.tsfor plan-mode integration;src/tests/live-capability-*-audit.test.tsfor opt-in live SDK audits.
npm run build
npm run check
npm run test:run