Skip to content
C.W.K.
Stream
Lesson 01 of 05 · published

Permission Modes in Practice

~14 min · permissions, permission-mode, safety

Level 0Observer
0 XP0/64 lessons0/13 achievements
0/150 XP to next level150 XP to go0% complete

The modes, and what they actually do

permission_mode on ClaudeAgentOptions picks how permission requests are settled. 'default' prompts the user before any tool that has side effects. 'acceptEdits' auto-approves Write/Edit but still prompts for Bash. 'bypassPermissions' approves everything (use for trusted CI only). 'plan' puts the agent in dry-run mode — it plans actions without executing. 'dontAsk' never prompts and denies whatever was not pre-approved. The list grows with the SDK (newer releases also accept 'auto'), so read PermissionMode in the version you install.

Pick by who is on the other end

If a developer is at the keyboard reviewing each prompt, 'acceptEdits' often hits the right tradeoff (file edits flow, shell still asks). If the agent runs unattended but a person answers through your own UI, 'default' with a custom permission handler is the posture; if nobody answers at all, 'dontAsk' with a short allowed_tools list denies instead of hanging. 'bypassPermissions' is for trusted CI runners that already pass through review elsewhere.

Write your own permission handler

Beyond the modes, you can supply a callback that decides per-tool. The callback receives the tool name, the tool input, and a ToolPermissionContext, and returns PermissionResultAllow or PermissionResultDeny. It is only consulted for calls that would otherwise prompt: a tool you pre-approved in allowed_tools never reaches it, and neither does a shell command the CLI already treats as read-only, such as echo or ls. Test a handler with a command that writes. cwkPippa registers no such callback today, so this is the pattern for when a human UI sits on the other end, not a tour of its code.

Principle: Permission posture is a UX decision as much as a security decision. Match it to who is actually in the loop.

Code

Mode selection by context·python
from claude_agent_sdk import ClaudeAgentOptions

# Interactive developer pairing: accept edits silently, ask for shell.
dev_options = ClaudeAgentOptions(
    cwd="/Users/me/repo",
    permission_mode="acceptEdits",
)

# Server-side worker with a human somewhere: route every prompt to your UI.
server_options = ClaudeAgentOptions(
    cwd="/srv/jobs",
    permission_mode="default",
    can_use_tool=permission_handler,  # your function, next block
)

# Unattended and strict: nothing prompts; whatever is not pre-approved is denied.
batch_options = ClaudeAgentOptions(
    cwd="/srv/jobs",
    permission_mode="dontAsk",
    allowed_tools=["Read", "Grep"],
)

# Trusted CI in a sandboxed container: bypass, and narrow what exists instead.
ci_options = ClaudeAgentOptions(
    cwd="/workspace",
    permission_mode="bypassPermissions",
    tools=["Read", "Edit", "Bash"],  # the only built-in tools this run has
)

# Plan-only mode for dry runs.
plan_options = ClaudeAgentOptions(
    cwd="/srv",
    permission_mode="plan",
)
Custom permission handler·python
from claude_agent_sdk import (
    ClaudeAgentOptions,
    PermissionResultAllow,
    PermissionResultDeny,
    ToolPermissionContext,
)

DEMANDS_REVIEW = {"Bash", "Write"}

async def permission_handler(tool_name: str, tool_input: dict, context: ToolPermissionContext):
    if tool_name not in DEMANDS_REVIEW:
        return PermissionResultAllow()
    # Forward the request to your UI; the user clicks approve or deny.
    decision = await ask_user_in_ui(tool_name, tool_input)
    if decision == "approve":
        return PermissionResultAllow()
    return PermissionResultDeny(message="User denied via UI")

options = ClaudeAgentOptions(
    cwd="/srv",
    permission_mode="default",
    can_use_tool=permission_handler,  # never consulted for tools listed in allowed_tools
)

External links

Exercise

Audit one Agent SDK call in your project. Identify the permission_mode in use and one caller-fact that makes it the right choice. If the answer is 'I copy-pasted bypassPermissions', that is the bug to fix.
Hint
Think about who is the responsible human at runtime. The mode should match who can answer 'should I run this?'

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign in — Please sign in to comment.

No comments yet — be the first.