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.
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:
| Mode | Behavior |
|---|---|
| Default | Read tools auto-allowed, write/exec tools prompt user |
| Plan | All tools require approval, forces step-by-step review |
| Auto (YOLO) | All tools auto-allowed, no prompts |
| bypassPermissions | Subagent 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:
- The AI sees all tools — it knows what’s available and can plan accordingly.
- Execution is gated — the permission system sits between intent and action.
- 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 Permissions | With Permission System |
|---|---|
| All tools execute immediately | Gated execution based on trust level |
| No user oversight | User can review each action |
| Security by restriction (hide tools) | Security by permission (expose all, gate execution) |
| All-or-nothing trust | Granular 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.