The Tool Lifecycle

How tools are registered, discovered, restricted, and executed in Claude Code — from build-time assembly through streaming concurrent execution.

Registration: assembleToolPool

Tools are assembled at startup in src/tools.ts via assembleToolPool(). The pool combines built-in tools and MCP tools with a cache-stable ordering that's critical for prompt cache economics:

[built-in tools sorted alphabetically] + [MCP tools sorted alphabetically]

Built-in tools form a contiguous prefix. MCP tools follow. This partition boundary is load-bearing — the server places a global cache breakpoint after the last built-in tool. A flat interleaved sort would invalidate all downstream cache keys on any MCP tool addition.

Each tool registers via the Tool interface (src/Tool.ts), providing: name, description, input schema (Zod), call function, and behavioral flags (isConcurrencySafe, interruptBehavior, isReadOnly).

Discovery: tool search and deferred loading

Not all tools are loaded into context at once. The ToolSearch system allows tools to be deferred — their schemas aren't loaded until the model requests them. This keeps the system prompt smaller and reduces cache churn.

When a tool is deferred, only its name appears in a <system-reminder>. The model calls ToolSearch with a query, which fuzzy-matches against deferred tool names and returns their full schemas. Once fetched, the tool is callable for the rest of the session.

Tools from MCP servers follow the same pattern: connected servers register their tools, which appear alongside built-in tools in the pool.

Restriction: context-aware tool filtering

Multiple layers filter which tools are available:

  1. Build-time feature flags — entire tool categories gated by feature() (e.g., WEB_BROWSER_TOOL, MONITOR_TOOL)
  2. Permission modetoolPermissionContext in AppState controls which tools require approval
  3. Agent-specific filtering — sub-agents receive a subset of parent tools. Read-only agents (Explore, Plan) get reduced tool sets. Coordinator workers lose coordinator-internal tools (TeamCreate, SendMessage).
  4. Deny rules — user and policy settings can deny specific tools or tool+input patterns
  5. MCP policy limits — enterprise policy can restrict which MCP tools are available

The canUseTool function in ToolUseContext chains these checks. A tool call that passes all checks executes; one that fails receives a denial as a tool_result error (deny-and-continue pattern).

Execution: the StreamingToolExecutor

The execution engine is where Claude Code gets its speed. The StreamingToolExecutor starts executing tools while the API is still generating a response.

Concurrency model

Each tool invocation is evaluated for concurrency safety: - Read-only tools (Read, Grep, Glob, WebFetch) — isConcurrencySafe: true — run in parallel (max 10) - State-mutating tools (Edit, Write, Bash writes) — isConcurrencySafe: false — run serially - BashTool — evaluated per commandgit log is concurrent-safe, git push is not

The partitioning algorithm groups consecutive same-safety tools into batches. Safe batches run concurrently via Promise.race(). Unsafe batches run one at a time.

Streaming integration

Three phases per turn: 1. During streaming — as tool_use blocks arrive from the API, they're immediately registered and started if safe 2. Between API messages — completed results are yielded in request order (out-of-order execution, in-order delivery) 3. After streaming — remaining results consumed via getRemainingResults() loop

Error cascade

Only Bash errors cancel sibling tools (other tools fail independently). A dedicated siblingAbortController kills running sibling processes without ending the conversation turn.

Permission checking: the pipeline

Every tool invocation passes through a four-layer permission pipeline:

Layer Cost Decision
1. Existing permission rules Zero — rule lookup Glob/regex match on tool+input
2. acceptEdits simulation Zero — mode check Would file-edit mode auto-approve?
3. Read-only whitelist Zero — list check Read, Grep, Glob, etc. — always allow
4. ML safety classifier High — Sonnet inference Two-stage: fast filter (8.5% FPR) → chain-of-thought (0.4% FPR)

The ML classifier is reached only when all three cheaper checks are inconclusive. Most tool calls never hit it.

Hooks integration

Every tool invocation fires PreToolUse (before) and PostToolUse (after) hooks. PreToolUse hooks can block the call (exit code 2), modify input (updatedInput), or override permissions (permissionDecision). This is the extension point for user-defined automation, linting, and audit logging.

The full path

Model emits tool_use block
  → StreamingToolExecutor registers tool
  → evaluates isConcurrencySafe
  → processQueue checks concurrency rules
  → if safe + slot available: start immediately (even during streaming)
  → tool.call() invoked with ToolUseContext
    → PreToolUse hooks fire (can block/modify)
    → permission pipeline checks (4 layers)
    → actual tool execution
    → PostToolUse hooks fire
  → result buffered
  → yielded in request order when all prior tools complete

Cross-references