The Claude Agent SDK documents a six-step permission flow — hooks, deny rules, ask rules, permission mode, allow rules, then a canUseTool callback. A call resolved before the callback does not need a host decision. PreToolUse and PermissionRequest are different hook events: choosing one depends on whether you need to inspect a supported tool call or handle a permission decision.
This article reviews Anthropic's permission configuration and hook reference checked on 19 October 2026. It describes documented behavior, not a runtime test of every SDK version; record both your SDK and Claude Code versions before applying it.
What the permission model actually is
Anthropic's permission documentation describes six stages. A call may be denied or approved before it reaches the callback; a hook's allow result still leaves deny and ask rules in effect.
Order matters here in a specific, non-obvious way: a hook returning allow does not skip the deny or ask steps that come after it in the sequence. A hook can approve something at its own stage, but a later deny rule can still block it — the steps are additive filters, not a first-match-wins chain that stops as soon as one step says yes.
- Pre-tool hooks inspect supported tool calls before the later permission checks, including in
bypassPermissionsmode.PreToolUsehas the event and matcher limits described below; it is not a record of every way data enters a session. - Deny rules are evaluated next and block matching tool calls outright.
- Ask rules prompt for explicit approval on matching calls.
- Permission mode is applied — the session-wide setting (below) that governs default behavior for anything not already matched by an earlier rule.
- Allow rules approve matching calls that survived the earlier steps.
- The
canUseToolcallback runs last, for whatever remains unresolved after every prior step.
The six permission modes
The SDK exposes six modes, each governing how much gets auto-approved before a call would otherwise reach the canUseTool callback (Claude Docs, as of late August 2026):
| Mode | Behavior |
|---|---|
default |
No mode-based auto-approvals; allow rules and calls that need no approval can still resolve before the callback |
dontAsk |
Denies anything not pre-approved; never calls the canUseTool callback |
acceptEdits |
Auto-approves supported, in-scope file edits and filesystem operations; protected and critical paths have exceptions |
bypassPermissions |
Approves nearly everything; the docs describe Claude as having "full system access" in this mode and warn to use it "with extreme caution" |
plan |
Read-only exploration; any edit still prompts for approval |
auto |
A model classifier approves or denies each request |
bypassPermissions is the mode most worth pausing on before enabling in any production configuration — not because it's undocumented, but because the two footguns below both compound specifically under it.
Footgun #1: allowed_tools doesn't constrain bypassPermissions
A team configuring allowed_tools=["Read"] alongside permissionMode: "bypassPermissions" might reasonably expect the tool list to still act as a ceiling. It doesn't. The documentation is explicit that bypassPermissions still approves every tool — including Bash, Write, and Edit — regardless of what allowed_tools specifies (Claude Docs, as of late August 2026). The docs' own stated fix is to use disallowed_tools instead when specific tools must remain blocked even under bypass mode — an allow-list and a bypass mode do not compose the way an allow-list intuitively suggests they should.
Footgun #2: subagents inherit the parent's permission mode
Subagents inherit the parent session's permission mode by default, and — this is the part worth flagging to anyone building multi-agent workflows on the SDK — an AgentDefinition's own permissionMode setting cannot override the parent when the parent session is running bypassPermissions, acceptEdits, or auto (Claude Docs, as of late August 2026). Concretely: a subagent configured with a tighter, more restrictive permission mode, running under a parent session in bypassPermissions mode, does not get the tighter mode. It inherits the parent's bypass instead.
The docs frame the risk directly: a subagent can carry "a different system prompt and less constrained behavior than your main agent," and under this inheritance rule it can end up with full, autonomous system access purely because of what mode the parent happened to be running — not because anyone explicitly granted the subagent that authority. For teams that treat subagents as a way to delegate narrower, lower-trust tasks, this inheritance behavior is the opposite of what that delegation pattern implies.
The callback isn't a universal safety net
A tool call auto-approved before the callback — via acceptEdits, bypassPermissions, or a matching allow rule — does not reach canUseTool. Put a check in PreToolUse when it must inspect supported calls regardless of whether they need approval, and verify that the hook's matcher covers the tools you intend. Use the permission-decision event when the task is handling an approval, rather than treating the callback as a check on all activity.
PermissionRequest, PreToolUse and canUseTool compared
Anthropic's SDK hook list includes PermissionRequest in both Python and TypeScript. The events serve different parts of the flow:
PreToolUse: inspect supported tool requests. It can inspect or deny a request before later permission checks, including calls that need no approval. The handler runs only when its matcher matches.EndConversationdoes not trigger this event; files inserted through prompt@references do not require a tool call and therefore do not trigger it either.PermissionRequest: handle permission decisions. It handles a request about to prompt, or a noninteractive request that would otherwise be denied. It does not inspect calls already approved by earlier controls, and does not fire for a sandboxed command's network-permission prompt. Neither this event norPreToolUsecoversEndConversation.canUseTool: let the SDK host decide. It handles calls that reach the host's approval handler; earlier auto-approvals skip it. Where a call reaches the host,PermissionRequesthooks run alongside it; the first decision applies. Do not assume the hook and callback form two sequential approvals.
The PermissionRequest reference also distinguishes its input from PreToolUse: both include the tool name and input, but PermissionRequest does not include tool_use_id. A hook approval cannot override a matching deny rule.
Version and configuration matter. The TypeScript option permissionPrompts: 'none' skips canUseTool at its stage while leaving PermissionRequest a chance to decide; absent a hook decision, the call is denied. Anthropic documents Claude Code v2.1.259 or later for this option. Omitting permissionMode also changed in TypeScript SDK v0.3.286: the session can now use a configured starting mode or the built-in default, which may be auto. Set the mode explicitly when your application requires default. These are documented version boundaries, not versions exercised in this article.
What this means for teams building on the SDK
The practical takeaway is that permission configuration on the Claude Agent SDK is a sequence to reason about, not a single setting to get right. Three checks follow directly from the two footguns above:
- If any part of your deployment runs
bypassPermissions, verify that anything you actually need blocked is indisallowed_tools, not just absent fromallowed_tools. - If your architecture spawns subagents, check what permission mode the parent session runs under before assuming a subagent's own
permissionModesetting is enforced — it may not be. - If authorization logic lives only in a
canUseToolcallback, confirm it's not being silently bypassed for tool calls an earlier step already approved; move enforcement you cannot afford to skip into aPreToolUsehook instead.
Review the mode, tool rules, hook matchers and host approval handler together. They cover different decisions; a handler that was never invoked cannot supply an authorization result. Include permitted, denied and approval-required cases in your own version-pinned integration checks before relying on the configuration.
This pattern — permission decisions evaluated through an ordered chain where an early "yes" can outrank a later, more specific check — is not unique to this SDK. It's the same class of misconfiguration risk covered generally in excessive agency controls for OWASP LLM06, and the same reasoning that applies to scoping any credentialed, tool-calling process applies here: default to the narrowest mode the task needs, and treat broader modes as an explicit, reviewed exception rather than a convenience default. The same discipline — one credential per agent, narrowly scoped, reviewed rather than assumed — is what the identity for AI agents guide covers for agent identity generally, and it applies just as directly to how permission modes are assigned per session and per subagent.
Framework-specific tool scoping decisions like these also intersect with the broader MCP tool-authorization surface: an agent's permission mode governs what the SDK itself allows, but any MCP servers it connects to carry their own tool-level authorization that should be reviewed independently — see the MCP server security checklist and the MCP server governance guide for that layer. And because subagents can carry distinct behavior from a main agent, the same vetting discipline documented for Agent Skills — know what a delegated unit of work can actually do before it runs — applies to subagent definitions as well.
FAQ
Does a deny rule always win over an allow rule in the Claude Agent SDK? Yes for calls it matches — deny rules are evaluated before allow rules in the six-step order, so a matching deny blocks the call regardless of what a later allow rule would have approved.
If I set allowed_tools to a short list, is that list enforced under every permission mode? No. Under bypassPermissions, the documented behavior is that every tool is approved regardless of allowed_tools; use disallowed_tools if specific tools must stay blocked in that mode.
Do subagents always run with the same permission mode as their parent? They inherit it by default, and cannot override it when the parent runs bypassPermissions, acceptEdits, or auto — a subagent's own permissionMode setting only takes effect outside those three parent modes.