1171 words Slides

17.5 Handling Permissions

Course: Claude Code - Enterprise Development

Section: Claude Agent SDK

Video Length: 3-4 minutes

Presenter: Daniel Treasure


Opening Hook

"Claude is powerful, but not all actions should be automatic. Writing files, executing code, accessing external systems—these need approval. The SDK gives you permission callbacks: Claude requests approval before acting. This is the heart of safe, human-in-the-loop automation."


Key Talking Points

What to say:

  • "The SDK has permission modes—ways to control what actions Claude can take without explicit approval."
  • "In CLI (4.7), you saw interactive mode prompts. In automation (14.3), you used dontAsk to skip prompts."
  • "In the SDK, there's 'delegate' mode: Claude requests permission via a callback, you decide programmatically."
  • "We'll show: restricted actions, permission callbacks, approve/deny logic, practical human-in-the-loop workflows."

What to show on screen:

  • Agent attempting restricted action (write file)
  • Permission callback triggered
  • Approval dialog or decision logic
  • Action allowed/denied based on callback response
  • Audit log of permission decisions

Demo Plan

[00:00 - 01:00] Permission Modes Overview 1. Show permission mode options: default, accept_edits, plan, bypass_permissions, delegate, dont_ask 2. Explain each: - default: prompt user interactively (slow for automation) - delegate: use permission callback - dont_ask: auto-approve (use with caution) - plan: show plan, approve once 3. Note: Which modes map to CLI behaviors from earlier videos

[01:00 - 02:00] Delegate Mode with Callback 1. Define permission callback function 2. Show callback signature: (action_type, resource, details) → bool 3. Implement: allow write to /tmp, deny write to /etc 4. Show Claude attempting restricted write 5. Callback denies, agent handles gracefully

[02:00 - 03:00] Human Approval Loop 1. Scenario: agent wants to execute deploy script 2. Permission callback pauses and waits for user input 3. Show: user prompted via terminal/GUI/API 4. User approves/denies 5. Agent continues or stops

[03:00 - 03:45] Practical Example: Code Review + Commit 1. Agent reviews code and suggests fixes 2. Agent wants to commit changes (restricted action) 3. Permission callback requests approval 4. If approved: agent commits 5. If denied: agent reports what it would have done


Code Examples & Commands

Python with delegate mode:

from claude_code import Agent, PermissionMode
import asyncio

def permission_callback(action_type: str, resource: str, details: dict) -> bool:
    """
    Decide whether to allow an action.
    Return True to allow, False to deny.
    """
    print(f"\n[Permission Request]")
    print(f"  Action: {action_type}")
    print(f"  Resource: {resource}")
    print(f"  Details: {details}")

    # Allow reads always
    if action_type == "read":
        return True

    # Allow writes only to /tmp
    if action_type == "write":
        return resource.startswith("/tmp/")

    # Deny everything else
    return False

async def main():
    agent = Agent(
        model="claude-sonnet-4-5-20250929",
        system_prompt="You are a code analyst.",
        permission_mode=PermissionMode.DELEGATE,
        permission_callback=permission_callback
    )

    # This will trigger permission callback
    result = await agent.run("Review the file at /var/important_config.txt and suggest improvements")
    print(f"\nResult: {result.output}")

asyncio.run(main())

Human-in-the-loop approval:

from claude_code import Agent, PermissionMode
import asyncio

def approval_callback(action_type: str, resource: str, details: dict) -> bool:
    """Request human approval for restricted actions."""

    # Allow reads without asking
    if action_type == "read":
        return True

    # For writes, ask human
    if action_type == "write":
        print(f"\n🔒 Human Approval Required")
        print(f"   Action: Write to {resource}")
        print(f"   Content preview: {details.get('preview', 'N/A')[:100]}...")

        response = input("\nApprove? (yes/no): ").strip().lower()
        return response == "yes"

    # Deny unknown actions
    return False

async def main():
    agent = Agent(
        model="claude-sonnet-4-5-20250929",
        permission_mode=PermissionMode.DELEGATE,
        permission_callback=approval_callback
    )

    result = await agent.run("Create a new documentation file explaining our API")
    print(f"Done: {result.output}")

asyncio.run(main())

Advanced: Role-based permissions:

from claude_code import Agent, PermissionMode
from typing import Optional

class RoleBasedPermissions:
    def __init__(self, user_role: str):
        self.user_role = user_role  # "admin", "developer", "viewer"

    def check(self, action_type: str, resource: str, details: dict) -> bool:
        if self.user_role == "viewer":
            # Viewers can only read
            return action_type == "read"

        elif self.user_role == "developer":
            # Developers can read and write to /src, /tests
            if action_type == "read":
                return True
            if action_type == "write":
                return resource.startswith(("/src/", "/tests/"))
            return False

        elif self.user_role == "admin":
            # Admins can do anything
            return True

        return False

# Usage
permissions = RoleBasedPermissions("developer")
agent = Agent(
    model="claude-sonnet-4-5-20250929",
    permission_mode=PermissionMode.DELEGATE,
    permission_callback=permissions.check
)

TypeScript version:

import Anthropic from "@anthropic-ai/sdk";

interface PermissionRequest {
  actionType: string;
  resource: string;
  details: Record<string, any>;
}

function permissionCallback(request: PermissionRequest): boolean {
  console.log(`\n[Permission Request]`);
  console.log(`  Action: ${request.actionType}`);
  console.log(`  Resource: ${request.resource}`);

  // Allow reads
  if (request.actionType === "read") {
    return true;
  }

  // Allow writes only to /tmp
  if (request.actionType === "write") {
    return request.resource.startsWith("/tmp/");
  }

  // Deny by default
  return false;
}

async function main(): Promise<void> {
  const client = new Anthropic({
    // SDK configuration with permission handling
  });

  console.log("Agent running with permission callbacks...");
  // Detailed implementation depends on SDK API
}

main().catch(console.error);

Gotchas & Tips

Gotcha 1: Blocking Callbacks - If callback takes too long (user thinking), agent waits - Solution: Set timeout on approval (auto-deny after 60 seconds)

Gotcha 2: Audit Trail - Permission decisions should be logged for compliance - Solution: Log all permission_callback calls with timestamp, decision, reason

Gotcha 3: Recursive Permissions - If callback itself triggers an agent action, avoid infinite loops - Solution: Use different permission mode for callback actions

Gotcha 4: Role Misalignment - Permissions in callback may not match actual system permissions - Example: callback allows write, but file is read-only - Solution: Permissions callback is first check; system permissions are second

Tip 1: Start Restrictive - Default-deny model: allow only known-safe actions - Easier to add permissions than restrict later

Tip 2: Context in Details - Pass context in details dict so callback can make informed decisions - Example: "user_id": 42, "request_source": "api", "is_test": false

Tip 3: Logging & Audit - Log every permission decision: who, what, when, allow/deny, reason - Critical for compliance and debugging

Tip 4: Testing Permissions - Write unit tests for permission_callback logic - Test edge cases: missing details, malformed requests

Tip 5: Cascade Approvals - For high-impact actions, require multiple approvals - Example: write to production → requires 2 approvals


Lead-out

"You've seen how to build safe, human-in-the-loop workflows using permission callbacks. Next: session management. Long-running agents need to persist state across interactions. We'll show how to create, resume, and manage agent sessions."


Reference URLs

  • Claude Code Permission Modes: [check official docs]
  • GitHub Issue #14297 (delegate mode): https://github.com/anthropics/mcp-servers/issues/14297
  • Human-in-the-Loop Patterns: [AI safety resources]

Prep Reading

  • Review permission modes in current SDK (5 min)
  • Understand your application's security requirements (5 min)
  • Plan which actions should require approval in your workflow (5 min)

Notes for Daniel

  • Security emphasis: This video is about trust. Emphasize that delegation with callbacks is how you build safe AI agents.
  • Tie to earlier videos: Mention 4.7 (interactive modes), 14.3 (dontAsk), and now 17.5 (delegate) form a progression of permission control.
  • Real-world example: Use a practical scenario: code review + commit, data pipeline, automated deployment. Show the approval step clearly.
  • GitHub issue: Mention #14297 is still being finalized—delegate mode is powerful but still evolving.
  • Production ready: Position this as the mechanism for production automation where humans remain in control.