CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-claude-agent-sdk

Python SDK for programmatically interacting with Claude Code, enabling AI-powered automation workflows with support for bidirectional conversations, custom in-process tools, hooks, and fine-grained permission control

Quality

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

The risk profile of this skill

This plugin was archived by the owner on Jun 3, 2026

Reason: Retiring all tiles created prior to the transition to plugin support

Overview
Eval results
Files

permissions.mddocs/

Permission Control

Permission control provides fine-grained control over which tools Claude can execute. You can use permission modes for broad control or implement custom permission callbacks for precise per-tool decisions.

Capabilities

Permission Modes

Predefined permission modes for controlling tool execution.

PermissionMode = Literal["default", "acceptEdits", "plan", "bypassPermissions"]
"""
Permission modes for tool execution.

Permission modes provide preset configurations for how Claude handles
tool permissions:

- 'default': Prompt user for dangerous operations (interactive mode)
- 'acceptEdits': Auto-accept file edits, prompt for other dangerous operations
- 'plan': Generate execution plan without actually executing tools
- 'bypassPermissions': Allow all tools without prompting (use with extreme caution)

Used in ClaudeAgentOptions.permission_mode.
"""

Permission Result Types

Result types returned by permission callbacks.

PermissionResult = PermissionResultAllow | PermissionResultDeny
"""
Union of permission result types.

Permission callbacks return either PermissionResultAllow or PermissionResultDeny
to indicate whether a tool should be allowed or blocked.
"""

Permission Result Allow

Allow a tool to execute, optionally with modifications.

@dataclass
class PermissionResultAllow:
    """
    Allow permission result.

    Indicates that a tool should be allowed to execute. Optionally includes
    modifications to the tool input or permission updates.

    Attributes:
        behavior: Always "allow"
        updated_input: Modified tool input parameters
        updated_permissions: Permission updates to apply
    """

    behavior: Literal["allow"] = "allow"
    """Behavior marker.

    Always set to "allow" to indicate permission is granted.
    """

    updated_input: dict[str, Any] | None = None
    """Modified tool input.

    If set, the tool will be executed with these input parameters instead
    of the original ones. Allows you to modify tool behavior on-the-fly.

    Example:
        # Original input: {"command": "rm -rf /"}
        # Modified input: {"command": "echo 'blocked dangerous command'"}
        updated_input={"command": "echo 'blocked dangerous command'"}
    """

    updated_permissions: list[PermissionUpdate] | None = None
    """Permission updates to apply.

    List of permission updates to apply after allowing this tool use.
    Can add rules, change modes, update directories, etc.

    See PermissionUpdate for details.
    """

Permission Result Deny

Deny a tool execution.

@dataclass
class PermissionResultDeny:
    """
    Deny permission result.

    Indicates that a tool should not be allowed to execute. Includes
    a message explaining why and whether to interrupt the conversation.

    Attributes:
        behavior: Always "deny"
        message: Explanation for denial
        interrupt: Whether to interrupt the conversation
    """

    behavior: Literal["deny"] = "deny"
    """Behavior marker.

    Always set to "deny" to indicate permission is denied.
    """

    message: str = ""
    """Denial message.

    Human-readable explanation of why the tool was blocked. This message
    may be shown to the user or logged.

    Example:
        "Dangerous command blocked for security reasons"
        "Tool not available in readonly mode"
    """

    interrupt: bool = False
    """Interrupt flag.

    If True, the conversation will be interrupted immediately after denying
    this tool. If False, Claude may try alternative approaches.

    Use True for critical security issues, False for normal denials.
    """

Permission Update

Configuration for updating permissions during execution.

@dataclass
class PermissionUpdate:
    """
    Permission update configuration.

    Defines a change to permission settings. Can add/remove rules, change
    modes, or modify directory access. Used in PermissionResultAllow.

    Attributes:
        type: Type of permission update
        rules: Permission rules (for rule-based updates)
        behavior: Permission behavior (for rule-based updates)
        mode: Permission mode (for mode updates)
        directories: Directories (for directory updates)
        destination: Where to save the update
    """

    type: Literal[
        "addRules",
        "replaceRules",
        "removeRules",
        "setMode",
        "addDirectories",
        "removeDirectories",
    ]
    """Update type.

    Specifies what kind of permission change to make:
    - 'addRules': Add new permission rules
    - 'replaceRules': Replace existing permission rules
    - 'removeRules': Remove permission rules
    - 'setMode': Change the permission mode
    - 'addDirectories': Add directories to allowed list
    - 'removeDirectories': Remove directories from allowed list
    """

    rules: list[PermissionRuleValue] | None = None
    """Permission rules.

    List of rules to add, replace, or remove. Required for rule-based
    update types (addRules, replaceRules, removeRules).

    See PermissionRuleValue for rule structure.
    """

    behavior: PermissionBehavior | None = None
    """Permission behavior.

    Behavior to apply to rules: "allow", "deny", or "ask".
    Required for rule-based update types.
    """

    mode: PermissionMode | None = None
    """Permission mode.

    The mode to set. Required for 'setMode' update type.
    """

    directories: list[str] | None = None
    """Directories list.

    List of directory paths. Required for directory-based update types
    (addDirectories, removeDirectories).
    """

    destination: PermissionUpdateDestination | None = None
    """Update destination.

    Where to save this permission update:
    - 'userSettings': User-level settings
    - 'projectSettings': Project-level settings
    - 'localSettings': Local settings
    - 'session': Current session only

    If None, applies to current session only.
    """

    def to_dict(self) -> dict[str, Any]:
        """
        Convert PermissionUpdate to dictionary format.

        Converts the dataclass to a dictionary matching the TypeScript
        control protocol format.

        Returns:
            Dictionary representation of the permission update
        """

Permission Rule Value

Individual permission rule configuration.

@dataclass
class PermissionRuleValue:
    """
    Permission rule value.

    Defines a single permission rule for a tool.

    Attributes:
        tool_name: Name of the tool this rule applies to
        rule_content: Optional rule details or pattern
    """

    tool_name: str
    """Tool name.

    The name of the tool this rule applies to. Examples:
    - "Bash"
    - "Write"
    - "Read"
    - "custom_tool"
    """

    rule_content: str | None = None
    """Rule content.

    Optional additional details about the rule. Can be:
    - A pattern to match against tool input
    - A condition to evaluate
    - Additional metadata

    Format depends on the tool and use case.
    """

Permission Behavior

Behavior types for permission rules.

PermissionBehavior = Literal["allow", "deny", "ask"]
"""
Permission behaviors for rules.

- 'allow': Always allow the tool
- 'deny': Always deny the tool
- 'ask': Prompt the user for decision
"""

Permission Update Destination

Where to save permission updates.

PermissionUpdateDestination = Literal[
    "userSettings", "projectSettings", "localSettings", "session"
]
"""
Permission update destinations.

Specifies where to persist permission updates:
- 'userSettings': Save to user-level settings (all projects)
- 'projectSettings': Save to project-level settings (current project)
- 'localSettings': Save to local settings (current directory)
- 'session': Apply only to current session (not persisted)
"""

Tool Permission Context

Context information passed to permission callbacks.

@dataclass
class ToolPermissionContext:
    """
    Context information for tool permission callbacks.

    Provides additional context when making permission decisions.

    Attributes:
        signal: Abort signal support (future feature)
        suggestions: Permission suggestions from CLI
    """

    signal: Any | None = None
    """Future: abort signal support.

    Reserved for future use to allow canceling permission checks.
    Currently always None.
    """

    suggestions: list[PermissionUpdate] = field(default_factory=list)
    """Permission suggestions from CLI.

    The CLI may provide suggested permission updates based on the
    tool use context. Your callback can choose to apply these
    suggestions or make its own decisions.
    """

Can Use Tool Callback

Type alias for tool permission callbacks.

CanUseTool = Callable[
    [str, dict[str, Any], ToolPermissionContext],
    Awaitable[PermissionResult]
]
"""
Tool permission callback type.

An async function that determines whether a specific tool use should
be allowed. Called before each tool execution.

Args:
    tool_name: Name of the tool Claude wants to use
    tool_input: Input parameters for the tool
    context: Additional context including suggestions

Returns:
    PermissionResult (either PermissionResultAllow or PermissionResultDeny)

Example:
    async def can_use_tool(
        tool_name: str,
        tool_input: dict[str, Any],
        context: ToolPermissionContext
    ) -> PermissionResult:
        if tool_name == "Bash" and "rm" in tool_input.get("command", ""):
            return PermissionResultDeny(
                message="Dangerous command blocked",
                interrupt=False
            )
        return PermissionResultAllow()
"""

Usage Examples

Using Permission Modes

from claude_agent_sdk import ClaudeAgentOptions, query

# Default mode - prompt for dangerous operations
options = ClaudeAgentOptions(
    permission_mode='default',
    allowed_tools=["Read", "Write", "Bash"]
)

# Accept edits mode - auto-accept file changes
options = ClaudeAgentOptions(
    permission_mode='acceptEdits',
    allowed_tools=["Read", "Write", "Edit"]
)

# Plan mode - generate plan without execution
options = ClaudeAgentOptions(
    permission_mode='plan',
    allowed_tools=["Read", "Write", "Bash"]
)

# Bypass permissions - allow everything (use with caution!)
options = ClaudeAgentOptions(
    permission_mode='bypassPermissions',
    allowed_tools=["Read", "Write", "Bash"]
)

async for msg in query(prompt="Modify files", options=options):
    print(msg)

Basic Permission Callback

from claude_agent_sdk import (
    ClaudeAgentOptions, query, PermissionResultAllow,
    PermissionResultDeny, ToolPermissionContext
)

async def can_use_tool(
    tool_name: str,
    tool_input: dict,
    context: ToolPermissionContext
) -> PermissionResult:
    """Simple permission callback."""
    # Allow Read operations
    if tool_name == "Read":
        return PermissionResultAllow()

    # Block Bash commands
    if tool_name == "Bash":
        return PermissionResultDeny(
            message="Bash commands not allowed",
            interrupt=False
        )

    # Allow everything else
    return PermissionResultAllow()

options = ClaudeAgentOptions(
    allowed_tools=["Read", "Write", "Bash"],
    can_use_tool=can_use_tool
)

async for msg in query(prompt="Analyze files", options=options):
    print(msg)

Blocking Dangerous Commands

from claude_agent_sdk import (
    ClaudeAgentOptions, query, PermissionResultAllow,
    PermissionResultDeny, ToolPermissionContext
)

async def block_dangerous_commands(
    tool_name: str,
    tool_input: dict,
    context: ToolPermissionContext
):
    """Block dangerous bash commands."""
    if tool_name == "Bash":
        command = tool_input.get("command", "")

        dangerous_patterns = [
            "rm -rf",
            "dd if=",
            "mkfs",
            "> /dev/",
            "chmod 777"
        ]

        for pattern in dangerous_patterns:
            if pattern in command:
                return PermissionResultDeny(
                    message=f"Blocked dangerous command pattern: {pattern}",
                    interrupt=True  # Stop immediately
                )

    return PermissionResultAllow()

options = ClaudeAgentOptions(
    allowed_tools=["Bash"],
    can_use_tool=block_dangerous_commands
)

async for msg in query(prompt="Clean up files", options=options):
    print(msg)

Modifying Tool Input

from claude_agent_sdk import (
    ClaudeAgentOptions, query, PermissionResultAllow, ToolPermissionContext
)

async def modify_bash_commands(
    tool_name: str,
    tool_input: dict,
    context: ToolPermissionContext
):
    """Modify bash commands to add safety flags."""
    if tool_name == "Bash":
        command = tool_input.get("command", "")

        # Add -i flag to rm commands for interactive prompts
        if "rm" in command and "-i" not in command:
            modified_command = command.replace("rm ", "rm -i ")
            return PermissionResultAllow(
                updated_input={"command": modified_command}
            )

    return PermissionResultAllow()

options = ClaudeAgentOptions(
    allowed_tools=["Bash"],
    can_use_tool=modify_bash_commands
)

async for msg in query(prompt="Delete old files", options=options):
    print(msg)

Path Restrictions

from pathlib import Path
from claude_agent_sdk import (
    ClaudeAgentOptions, query, PermissionResultAllow,
    PermissionResultDeny, ToolPermissionContext
)

ALLOWED_PATHS = ["/home/user/project", "/tmp"]

async def enforce_path_restrictions(
    tool_name: str,
    tool_input: dict,
    context: ToolPermissionContext
):
    """Restrict file operations to allowed paths."""
    if tool_name in ["Read", "Write", "Edit"]:
        file_path = tool_input.get("file_path", "")
        abs_path = Path(file_path).resolve()

        # Check if path is within allowed directories
        allowed = any(
            str(abs_path).startswith(str(Path(allowed_path).resolve()))
            for allowed_path in ALLOWED_PATHS
        )

        if not allowed:
            return PermissionResultDeny(
                message=f"Access denied: {file_path} is outside allowed paths",
                interrupt=False
            )

    return PermissionResultAllow()

options = ClaudeAgentOptions(
    allowed_tools=["Read", "Write", "Edit"],
    can_use_tool=enforce_path_restrictions
)

async for msg in query(prompt="Modify configuration", options=options):
    print(msg)

Time-Based Restrictions

from datetime import datetime
from claude_agent_sdk import (
    ClaudeAgentOptions, query, PermissionResultAllow,
    PermissionResultDeny, ToolPermissionContext
)

async def business_hours_only(
    tool_name: str,
    tool_input: dict,
    context: ToolPermissionContext
):
    """Only allow certain tools during business hours."""
    hour = datetime.now().hour

    # Restrict Bash during off-hours (before 9am or after 5pm)
    if tool_name == "Bash" and (hour < 9 or hour >= 17):
        return PermissionResultDeny(
            message="Bash commands only allowed during business hours (9am-5pm)",
            interrupt=False
        )

    return PermissionResultAllow()

options = ClaudeAgentOptions(
    allowed_tools=["Bash", "Read", "Write"],
    can_use_tool=business_hours_only
)

async for msg in query(prompt="Run deployment script", options=options):
    print(msg)

Logging All Tool Uses

import logging
from claude_agent_sdk import (
    ClaudeAgentOptions, query, PermissionResultAllow, ToolPermissionContext
)

logging.basicConfig(level=logging.INFO)

async def log_and_allow(
    tool_name: str,
    tool_input: dict,
    context: ToolPermissionContext
):
    """Log all tool uses and allow them."""
    logging.info(f"Tool use: {tool_name}")
    logging.info(f"Input: {tool_input}")

    return PermissionResultAllow()

options = ClaudeAgentOptions(
    allowed_tools=["Read", "Write", "Bash"],
    can_use_tool=log_and_allow
)

async for msg in query(prompt="Analyze project", options=options):
    print(msg)

User Confirmation

from claude_agent_sdk import (
    ClaudeAgentOptions, query, PermissionResultAllow,
    PermissionResultDeny, ToolPermissionContext
)

async def ask_user_permission(
    tool_name: str,
    tool_input: dict,
    context: ToolPermissionContext
):
    """Ask user for confirmation on file writes."""
    if tool_name in ["Write", "Edit"]:
        file_path = tool_input.get("file_path", "unknown")

        print(f"\nClaude wants to modify: {file_path}")
        print(f"Tool: {tool_name}")

        # In a real application, you'd use a proper input method
        # This is just for demonstration
        response = input("Allow? (y/n): ")

        if response.lower() != 'y':
            return PermissionResultDeny(
                message="User denied permission",
                interrupt=False
            )

    return PermissionResultAllow()

options = ClaudeAgentOptions(
    allowed_tools=["Read", "Write", "Edit"],
    can_use_tool=ask_user_permission
)

async for msg in query(prompt="Fix bugs", options=options):
    print(msg)

Permission Updates

from claude_agent_sdk import (
    ClaudeAgentOptions, query, PermissionResultAllow,
    PermissionUpdate, PermissionRuleValue, ToolPermissionContext
)

async def add_permission_after_use(
    tool_name: str,
    tool_input: dict,
    context: ToolPermissionContext
):
    """Add permission rule after first use."""
    if tool_name == "Read":
        # After first read, allow all future reads automatically
        return PermissionResultAllow(
            updated_permissions=[
                PermissionUpdate(
                    type="addRules",
                    rules=[PermissionRuleValue(tool_name="Read")],
                    behavior="allow",
                    destination="session"  # Session only
                )
            ]
        )

    return PermissionResultAllow()

options = ClaudeAgentOptions(
    allowed_tools=["Read", "Write"],
    can_use_tool=add_permission_after_use
)

async for msg in query(prompt="Read multiple files", options=options):
    print(msg)

Rate Limiting

from datetime import datetime, timedelta
from claude_agent_sdk import (
    ClaudeAgentOptions, query, PermissionResultAllow,
    PermissionResultDeny, ToolPermissionContext
)

# Rate limiting state
tool_usage = {}
RATE_LIMIT = 10  # Max 10 uses per minute

async def rate_limit_tools(
    tool_name: str,
    tool_input: dict,
    context: ToolPermissionContext
):
    """Rate limit tool usage."""
    if tool_name not in tool_usage:
        tool_usage[tool_name] = []

    now = datetime.now()

    # Remove old entries
    tool_usage[tool_name] = [
        ts for ts in tool_usage[tool_name]
        if now - ts < timedelta(minutes=1)
    ]

    # Check limit
    if len(tool_usage[tool_name]) >= RATE_LIMIT:
        return PermissionResultDeny(
            message=f"Rate limit exceeded for {tool_name} ({RATE_LIMIT}/min)",
            interrupt=False
        )

    # Record usage
    tool_usage[tool_name].append(now)

    return PermissionResultAllow()

options = ClaudeAgentOptions(
    allowed_tools=["Bash"],
    can_use_tool=rate_limit_tools
)

async for msg in query(prompt="Run many tests", options=options):
    print(msg)

Conditional Permissions

from claude_agent_sdk import (
    ClaudeAgentOptions, query, PermissionResultAllow,
    PermissionResultDeny, ToolPermissionContext
)

class AppState:
    def __init__(self):
        self.readonly = False
        self.trusted = False

state = AppState()

async def conditional_permissions(
    tool_name: str,
    tool_input: dict,
    context: ToolPermissionContext
):
    """Apply conditional permissions based on app state."""
    # Block writes in readonly mode
    if state.readonly and tool_name in ["Write", "Edit", "Bash"]:
        return PermissionResultDeny(
            message="System is in readonly mode",
            interrupt=False
        )

    # Allow everything if trusted
    if state.trusted:
        return PermissionResultAllow()

    # Otherwise, apply normal rules
    if tool_name == "Bash":
        return PermissionResultDeny(
            message="Bash not allowed in untrusted mode",
            interrupt=False
        )

    return PermissionResultAllow()

options = ClaudeAgentOptions(
    allowed_tools=["Read", "Write", "Bash"],
    can_use_tool=conditional_permissions
)

# Enable readonly mode
state.readonly = True

async for msg in query(prompt="Analyze code", options=options):
    print(msg)

Using Permission Suggestions

from claude_agent_sdk import (
    ClaudeAgentOptions, query, PermissionResultAllow, ToolPermissionContext
)

async def use_suggestions(
    tool_name: str,
    tool_input: dict,
    context: ToolPermissionContext
):
    """Use permission suggestions from CLI."""
    # Check if CLI provided suggestions
    if context.suggestions:
        print(f"CLI suggests {len(context.suggestions)} permission updates")

        # Accept suggestions
        return PermissionResultAllow(
            updated_permissions=context.suggestions
        )

    return PermissionResultAllow()

options = ClaudeAgentOptions(
    allowed_tools=["Read", "Write"],
    can_use_tool=use_suggestions
)

async for msg in query(prompt="Work on project", options=options):
    print(msg)

docs

agent-definitions.md

agents.md

client.md

configuration-options.md

content-blocks.md

core-query-interface.md

custom-tools.md

error-handling.md

errors.md

hook-system.md

hooks.md

index.md

mcp-config.md

mcp-server-configuration.md

messages-and-content.md

messages.md

options.md

permission-control.md

permissions.md

query.md

transport.md

COMPLETION_SUMMARY.md

tile.json