Skip to content

Agent Sdk Permissions

Updated 3 days ago
<style> {` .edgeLabel { padding: 8px 12px !important; } .edgeLabel rect { rx: 4; ry: 4; stroke: #D9D8D5 !important; stroke-width: 1px !important; } /* Add rounded corners to flowchart nodes */ .node rect { rx: 8 !important; ry: 8 !important; } `} </style>

SDK Permissions

The Claude Agent SDK provides powerful permission controls that allow you to manage how Claude uses tools in your application.

This guide covers how to implement permission systems using the canUseTool callback, hooks, and settings.json permission rules. For complete API documentation, see the TypeScript SDK reference.

Overview

The Claude Agent SDK provides four complementary ways to control tool usage:

  1. Permission Modes - Global permission behavior settings that affect all tools
  2. canUseTool callback - Runtime permission handler for cases not covered by other rules
  3. Hooks - Fine-grained control over every tool execution with custom logic
  4. Permission rules (settings.json) - Declarative allow/deny rules with integrated bash command parsing

Use cases for each approach:

  • Permission modes - Set overall permission behavior (planning, auto-accepting edits, bypassing checks)
  • canUseTool - Dynamic approval for uncovered cases, prompts user for permission
  • Hooks - Programmatic control over all tool executions
  • Permission rules - Static policies with intelligent bash command parsing

Permission Flow Diagram

%%{init: {"theme": "base", "themeVariables": {"edgeLabelBackground": "#F0F0EB", "lineColor": "#91918D"}, "flowchart": {"edgeLabelMarginX": 12, "edgeLabelMarginY": 8}}}%%
flowchart TD
    Start([Tool request]) --> PreHook(PreToolUse Hook)

    PreHook -->|  Allow  | Execute(Execute Tool)
    PreHook -->|  Deny  | Denied(Denied)
    PreHook -->|  Ask  | Callback(canUseTool Callback)
    PreHook -->|  Continue  | Deny(Check Deny Rules)

    Deny -->|  Match  | Denied
    Deny -->|  No Match  | Allow(Check Allow Rules)

    Allow -->|  Match  | Execute
    Allow -->|  No Match  | Ask(Check Ask Rules)

    Ask -->|  Match  | Callback
    Ask -->|  No Match  | Mode{Permission Mode?}

    Mode -->|  bypassPermissions  | Execute
    Mode -->|  Other modes  | Callback

    Callback -->|  Allow  | Execute
    Callback -->|  Deny  | Denied

    Denied --> DeniedResponse([Feedback to agent])

    Execute --> PostHook(PostToolUse Hook)
    PostHook --> Done([Tool Response])

    style Start fill:#F0F0EB,stroke:#D9D8D5,color:#191919

    style Denied fill:#BF4D43,color:#fff
    style DeniedResponse fill:#BF4D43,color:#fff
    style Execute fill:#DAAF91,color:#191919
    style Done fill:#DAAF91,color:#191919

    classDef hookClass fill:#CC785C,color:#fff
    class PreHook,PostHook hookClass

    classDef ruleClass fill:#EBDBBC,color:#191919
    class Deny,Allow,Ask ruleClass

    classDef modeClass fill:#A8DAEF,color:#191919
    class Mode modeClass

    classDef callbackClass fill:#D4A27F,color:#191919
    class Callback callbackClass

Processing Order: PreToolUse Hook → Deny Rules → Allow Rules → Ask Rules → Permission Mode Check → canUseTool Callback → PostToolUse Hook

Permission Modes

Permission modes provide global control over how Claude uses tools. You can set the permission mode when calling query() or change it dynamically during streaming sessions.

Available Modes

The SDK supports four permission modes, each with different behavior:

Mode Description Tool Behavior
default Standard permission behavior Normal permission checks apply
plan Planning mode - no execution Claude can only use read-only tools; presents a plan before execution (Not currently supported in SDK)
acceptEdits Auto-accept file edits File edits and filesystem operations are automatically approved
bypassPermissions Bypass all permission checks All tools run without permission prompts (use with caution)

Setting Permission Mode

You can set the permission mode in two ways:

1. Initial Configuration

Set the mode when creating a query:

<CodeGroup>

typescript
import { query } from "@anthropic-ai/claude-agent-sdk";

const result = await query({
  prompt: "Help me refactor this code",
  options: {
    permissionMode: 'default'  // Standard permission mode
  }
});

from claude_agent_sdk import query

result = await query( prompt="Help me refactor this code", options={ "permission_mode": "default" # Standard permission mode } )

</CodeGroup>

#### 2. Dynamic Mode Changes (Streaming Only)

Change the mode during a streaming session:

<CodeGroup>
```typescript
import { query } from "@anthropic-ai/claude-agent-sdk";

// Create an async generator for streaming input
async function* streamInput() {
  yield { 
    type: 'user',
    message: { 
      role: 'user', 
      content: "Let's start with default permissions" 
    }
  };
  
  // Later in the conversation...
  yield {
    type: 'user',
    message: {
      role: 'user',
      content: "Now let's speed up development"
    }
  };
}

const q = query({
  prompt: streamInput(),
  options: {
    permissionMode: 'default'  // Start in default mode
  }
});

// Change mode dynamically
await q.setPermissionMode('acceptEdits');

// Process messages
for await (const message of q) {
  console.log(message);
}

from claude_agent_sdk import query

async def stream_input(): """Async generator for streaming input""" yield { "type": "user", "message": { "role": "user", "content": "Let's start with default permissions" } }

  # Later in the conversation...
  yield {
      "type": "user",
      "message": {
          "role": "user",
          "content": "Now let's speed up development"
      }
  }

q = query( prompt=stream_input(), options={ "permission_mode": "default" # Start in default mode } )

Change mode dynamically

await q.set_permission_mode("acceptEdits")

Process messages

async for message in q: print(message)

</CodeGroup>

### Mode-Specific Behaviors

#### Accept Edits Mode (`acceptEdits`)

In accept edits mode:

* All file edits are automatically approved
* Filesystem operations (mkdir, touch, rm, etc.) are auto-approved
* Other tools still require normal permissions
* Speeds up development when you trust Claude's edits
* Useful for rapid prototyping and iterations

Auto-approved operations:

* File edits (Edit, Write tools)
* Bash filesystem commands (mkdir, touch, rm, mv, cp)
* File creation and deletion

#### Bypass Permissions Mode (`bypassPermissions`)

In bypass permissions mode:

* **ALL tool uses are automatically approved**
* No permission prompts appear
* Hooks still execute (can still block operations)
* **Use with extreme caution** - Claude has full system access
* Recommended only for controlled environments

### Mode Priority in Permission Flow

Permission modes are evaluated at a specific point in the permission flow:

1. **Hooks execute first** - Can allow, deny, ask, or continue
2. **Deny rules** are checked - Block tools regardless of mode
3. **Allow rules** are checked - Permit tools if matched
4. **Ask rules** are checked - Prompt for permission if matched
5. **Permission mode** is evaluated:
 * **`bypassPermissions` mode** - If active, allows all remaining tools
 * **Other modes** - Defer to `canUseTool` callback
6. **`canUseTool` callback** - Handles remaining cases

This means:

* Hooks can always control tool use, even in `bypassPermissions` mode
* Explicit deny rules override all permission modes
* Ask rules are evaluated before permission modes
* `bypassPermissions` mode overrides the `canUseTool` callback for unmatched tools

### Best Practices

1. **Use default mode** for controlled execution with normal permission checks
2. **Use acceptEdits mode** when working on isolated files or directories
3. **Avoid bypassPermissions** in production or on systems with sensitive data
4. **Combine modes with hooks** for fine-grained control
5. **Switch modes dynamically** based on task progress and confidence

Example of mode progression:

```typescript
// Start in default mode for controlled execution
permissionMode: 'default'

// Switch to acceptEdits for rapid iteration
await q.setPermissionMode('acceptEdits')

canUseTool

The canUseTool callback is passed as an option when calling the query function. It receives the tool name and input parameters, and must return a decision- either allow or deny.

canUseTool fires whenever Claude Code would show a permission prompt to a user, e.g. hooks and permission rules do not cover it and it is not in acceptEdits mode.

Here's a complete example showing how to implement interactive tool approval: