Skip to main content
Module 1: Core Agent 2 / 6
Beginner Session 2 Tools Permissions Security

Tool System & Permissions

Learn how Claude Code's tool dispatch system works — from tool definitions to permission callbacks that control what the AI can and cannot do.

March 20, 2026 20 min read

What You’ll Learn

Tools are how Claude Code interacts with your system. Without tools, it can only generate text. With tools, it can read files, edit code, run commands, search the web, and more.

By the end of this session, you’ll understand:

  • How tools are defined and registered
  • The tool dispatch pattern
  • Permission modes and approval gates
  • How to add custom tools safely

The Problem

Giving an AI unrestricted access to your filesystem and terminal is dangerous. It could delete files, run malicious commands, or expose sensitive data.

But being too restrictive makes the AI useless — if it can’t read files, it can’t help you code.

The solution: a permission system that acts as a gatekeeper between the AI’s intent and actual execution.

How It Works

Tool Definition

Each tool is defined with a name, description, and input schema:

interface Tool {
  name: string;
  description: string;
  input_schema: {
    type: "object";
    properties: Record<string, unknown>;
    required: string[];
  };
}

The tool definition is sent to the Claude API as part of the request. The AI sees the tool name, description, and schema, then decides whether and how to use it.

Tool Categories

Claude Code groups tools into categories with different trust levels:

┌──────────────────────────────────────────────┐
│              Tool Categories                  │
│                                               │
│  ┌─────────────────────────────────────────┐  │
│  │  Always Allowed (no prompt)             │  │
│  │  • Read (file reading)                  │  │
│  │  • Glob (file search)                   │  │
│  │  • Grep (content search)               │  │
│  │  • TodoRead (view tasks)                │  │
│  └─────────────────────────────────────────┘  │
│                                               │
│  ┌─────────────────────────────────────────┐  │
│  │  Requires Approval (user confirms)      │  │
│  │  • Write (create files)                 │  │
│  │  • Edit (modify files)                  │  │
│  │  • Bash (run commands)                  │  │
│  │  • NotebookEdit (modify notebooks)      │  │
│  └─────────────────────────────────────────┘  │
│                                               │
│  ┌─────────────────────────────────────────┐  │
│  │  Special (context-dependent)            │  │
│  │  • Agent (spawn subagent)               │  │
│  │  • WebFetch (access URLs)               │  │
│  │  • MCP tools (external servers)         │  │
│  └─────────────────────────────────────────┘  │
└──────────────────────────────────────────────┘

The Permission Callback

When the AI requests a tool call, the system checks permissions before executing:

AI requests tool_use


┌──────────────┐    ┌──────────────┐
│ Check         │───►│ Permission   │
│ Permission    │    │ Mode         │
│ Rules         │    │              │
└──────┬───────┘    └──────────────┘

       ├─── Allowed ──────► Execute tool

       ├─── Denied ───────► Return error to AI

       └─── Ask User ────► Show prompt

                      ┌───────┴───────┐
                      │               │
                    Allow           Deny
                      │               │
                   Execute        Return error

Permission Modes

Claude Code supports several permission modes:

ModeBehavior
DefaultRead tools auto-allowed, write/exec tools prompt user
PlanAll tools require approval, forces step-by-step review
Auto (YOLO)All tools auto-allowed, no prompts
bypassPermissionsSubagent mode, inherits parent permissions

The mode is set at session level and affects all tool calls:

# Default mode — prompts for writes and commands
claude

# Plan mode — review everything
claude --plan

# Auto mode — allow everything (use carefully!)
claude --dangerously-skip-permissions

Tool Dispatch

When the loop receives a tool_use block, it dispatches to the correct handler:

async function dispatchTool(name: string, input: unknown): Promise<string> {
  switch (name) {
    case "Read":
      return await readFile(input.file_path);
    case "Edit":
      return await editFile(input.file_path, input.old_string, input.new_string);
    case "Bash":
      return await runBash(input.command);
    case "Write":
      return await writeFile(input.file_path, input.content);
    case "Glob":
      return await globSearch(input.pattern);
    case "Grep":
      return await grepSearch(input.pattern, input.path);
    default:
      return `Unknown tool: ${name}`;
  }
}

The actual implementation is more sophisticated — it uses a tool registry pattern where each tool is an object with execute(), validateInput(), and getPermissionLevel() methods.

Key Insight

Tools are the AI’s hands, and permissions are the safety net. The brilliance of this design is that:

  1. The AI sees all tools — it knows what’s available and can plan accordingly.
  2. Execution is gated — the permission system sits between intent and action.
  3. Users control the trust level — from paranoid (plan mode) to full autonomy (auto mode).

This is fundamentally different from systems that hide tools from the AI. When Claude Code knows a tool exists but is denied, it can explain what it wanted to do and ask for permission — much better than silently failing.

Hands-On Example

Here’s how to add a custom tool to a minimal agent:

# Define a new tool
search_tool = {
    "name": "search_codebase",
    "description": "Search for a pattern across all files in the project",
    "input_schema": {
        "type": "object",
        "properties": {
            "pattern": {
                "type": "string",
                "description": "Regex pattern to search for"
            },
            "file_type": {
                "type": "string",
                "description": "File extension to filter (e.g., 'py', 'ts')"
            }
        },
        "required": ["pattern"]
    }
}

# Permission check
def check_permission(tool_name: str, input: dict) -> bool:
    """Simple permission gate."""
    # Read-only tools are always allowed
    if tool_name in ["read_file", "search_codebase", "list_files"]:
        return True

    # Write tools need confirmation
    print(f"Tool '{tool_name}' wants to: {input}")
    response = input("Allow? [y/N]: ")
    return response.lower() == 'y'

# In the agent loop, add permission checking:
for block in response.content:
    if block.type == "tool_use":
        if check_permission(block.name, block.input):
            result = execute_tool(block.name, block.input)
        else:
            result = f"Permission denied for {block.name}"

        tool_results.append({
            "type": "tool_result",
            "tool_use_id": block.id,
            "content": str(result),
        })

What Changed

Without PermissionsWith Permission System
All tools execute immediatelyGated execution based on trust level
No user oversightUser can review each action
Security by restriction (hide tools)Security by permission (expose all, gate execution)
All-or-nothing trustGranular trust per tool category

Next Session

In Session 3, we’ll explore Planning with TodoWrite — how Claude Code creates task plans before diving into execution, and why this “think before you act” pattern dramatically improves outcomes.