From Tool Calls to Real Execution: Inside Claude Code’s 43‑Tool Scheduling System
The article dissects Claude Code’s tool runtime, detailing how a unified TypeScript interface, a feature‑gated registry, a seven‑stage dispatch pipeline, and a concurrency scheduler transform model‑generated tool_use blocks into safe, parallel or serial system operations, including dynamic MCP extensions.
Claude Code bridges the gap between stateless large language models and real system actions by introducing a comprehensive tool runtime system that converts model‑generated tool_use blocks into executable operations.
Architecture Overview
The system consists of four layers: the Tool Interface , the Registry , the Dispatch Pipeline , and the Concurrency Scheduler . Each layer has a clear responsibility, and a tool passes through all four before reaching the operating system.
Tool Interface
All tools implement the same generic interface defined in Tool.ts. The interface accepts three type parameters – Input (a Zod schema), Output, and P (progress data). A typical definition looks like:
interface Tool<Input, Output, P> {
name: string;
description: string;
inputSchema: z.ZodSchema<Input>;
call(input: Input, context: ExecutionContext): Promise<ToolResult<Output>>;
isConcurrencySafe(input: Input): boolean;
isReadOnly(input: Input): boolean;
checkPermissions(input: Input, context: PermissionContext): Promise<PermissionResult>;
mapToolResultToToolResultBlockParam(result: ToolResult<Output>): ToolResultBlockParam;
}Tools are created via the buildTool() factory, which supplies conservative default values (fail‑closed). If a tool omits concurrency safety, it is executed serially; missing permission checks fall back to the default permission flow.
Tool Registry
All tools are registered in tools.ts through getAllBaseTools(), which returns a flat array. Sixteen tools are always available (file read/write/patch, bash, web_fetch, delegate_task, memory, etc.), while roughly twenty‑seven are conditionally enabled via feature flags, environment variables, or platform checks.
MCP (Model Context Protocol) Tools
External processes can expose tools via the MCP server. MCP tools are dynamically registered at runtime and wrapped to conform to the same Tool interface, allowing them to participate in the same dispatch pipeline and permission system.
Tool Pool Assembly
The final tool set is built in three steps: getAllBaseTools() – returns the built‑in list, applying feature gates. getTools(permissionContext) – filters the list with deny rules and isEnabled(). assembleToolPool(permissionContext, mcpTools) – merges built‑in and MCP tools, sorting alphabetically so that built‑in tools win name collisions.
const merged = [...builtInTools, ...mcpTools].sort((a, b) => a.name.localeCompare(b.name));This stable ordering improves prompt‑cache hit rates because the tool array is part of the API request.
API Serialization
Before sending tools to the Claude API, toolToAPISchema() converts each tool’s Zod schema into the JSON Schema format required by Anthropic.
Dispatch Pipeline – Seven Stages
When Claude returns a response containing tool_use blocks, the pipeline processes each block sequentially through the following stages:
Stage 1 – Extraction
const toolUseBlocks = message.content.filter(block => block.type === 'tool_use');Each block includes name, input, and a unique id that must be echoed back.
Stage 2 – Input Validation
The Zod schema validates the raw input via safeParse(). If validation fails, the model receives a formatted error and execution stops. Some tools also run a second validateInput() for semantic checks (e.g., absolute file paths).
Stage 3 – Pre‑Hook
User‑configured hooks run before permission checks. They can allow, deny, modify input, block execution, or provide extra context, but they never bypass deny rules defined in settings.json.
Stage 4 – Permission Check
The most complex stage evaluates multiple layers in order:
Deny rules – immediate stop.
Ask rules – prompt the user unless a Bash sandbox auto‑allows.
Tool‑specific checkPermissions() (e.g., BashTool parses sub‑commands).
Hard‑coded safety checks for sensitive paths (e.g., .git/).
Permission mode from user settings.
Allow rules – final pass if no deny/ask triggered.
Sources of rules include policySettings, localSettings, projectSettings, userSettings, flagSettings, CLI arguments, command‑line flags, and session data, allowing organization‑wide policies to override personal preferences.
Stage 5 – Execution
const result = await tool.call(
validatedInput,
executionContext,
permissionCallback,
parentAssistantMessage,
progressCallback
);The call receives five parameters: validated input, execution context (working directory, abort controller, app state), a permission callback for runtime permission requests, the parent assistant message, and a progress callback. The original model input is preserved for logging, while hooks may see a modified version.
Stage 6 – Post‑Hook
After execution, post‑hooks can modify the tool’s output, add context, or abort the conversation. A special PostToolUseFailure hook runs only on errors, giving external systems a chance to log or suggest remediation.
Stage 7 – Result Mapping
Each tool implements mapToolResultToToolResultBlockParam() to produce a ToolResultBlockParam that references the original tool_use_id. Large results are persisted to sessionDir/tool-results/{toolUseId}.txt and a preview with a file reference is sent to the API, preventing context overflow.
Concurrency Scheduler
When a single model message generates multiple tool calls, the scheduler batches them based on the isConcurrencySafe(input) flag. Safe batches run in parallel (up to ten concurrent tools, configurable via CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY); unsafe batches run serially. Context modifiers are applied only between batches.
For example, five independent read calls form one concurrent batch, while a subsequent write call starts a new serial batch.
Streaming Executor
The StreamingToolExecutor can start executing a tool as soon as its tool_use block is streamed, without waiting for the full model response. It follows the same concurrency rules but adds a Bash error‑cascading rule: if a Bash command fails, all sibling tools in the same batch are aborted, because subsequent operations may depend on a now‑invalid environment.
Practical Example
A model decides to read /src/index.ts and emits:
{
"type": "tool_use",
"id": "toolu_01XYZ",
"name": "read",
"input": { "file_path": "/src/index.ts" }
}The pipeline then extracts the block, finds FileReadTool, validates the input against its Zod schema, runs any pre‑hooks (none in this case), passes permission checks (generally allowed for reads), executes the tool (which may run cat -n), maps the result back to a tool_result referencing toolu_01XYZ, and finally appends the result to the conversation.
Key Takeaways
Model as Scheduler: The runtime trusts the model’s plan (parallelize independent reads, serialize writes) and merely enforces it.
Fail‑Closed Design: Unknown tools, invalid inputs, missing concurrency flags, or absent permissions all result in safe failure rather than permissive execution.
Hook Extensibility: Pre‑, post‑, and failure hooks provide controlled extension points that can tighten security but never relax it.
Uniform Interface: All 43+ tools share a 30‑method contract, simplifying routing and permission handling.
Overall, Claude Code’s tool system transforms structured tool_use intents into verified, permission‑checked, and efficiently scheduled real‑world actions.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Code Mala Tang
Read source code together, write articles together, and enjoy spicy hot pot together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
