Skip to content

Architecture Map

Claude Code follows a layered architecture where each layer has a clear responsibility. Understanding this map is the key to navigating the 512K+ line codebase efficiently.

graph TB
subgraph "CLI Layer"
CLI["cli.tsx — Bootstrap Entrypoint"]
MAIN["main.tsx — Commander.js CLI"]
end
subgraph "Command Layer"
CMDS["commands.ts — Command Registry"]
SLASH["60+ Slash Commands"]
SKILLS["Skills & Plugins"]
end
subgraph "Agentic Loop"
QE["QueryEngine — Session Manager"]
QUERY["query.ts — Turn Orchestrator"]
CLAUDE["claude.ts — API Streaming"]
end
subgraph "Tool System"
TOOLS["tools.ts — Tool Pool Assembly"]
TOOL_DEF["Tool.ts — Tool Interface"]
STE["StreamingToolExecutor"]
BUILTIN["30+ Built-in Tools"]
MCP["MCP Tools"]
end
subgraph "Infrastructure"
RETRY["withRetry.ts — Retry Logic"]
SETTINGS["settings.ts — Configuration"]
PERMS["permissions.ts — Security"]
COMPACT["compact.ts — Context Management"]
end
CLI --> MAIN
MAIN --> CMDS
CMDS --> SLASH
CMDS --> SKILLS
MAIN --> QE
QE --> QUERY
QUERY --> CLAUDE
CLAUDE --> RETRY
QUERY --> STE
STE --> BUILTIN
STE --> MCP
TOOLS --> BUILTIN
TOOLS --> MCP
QUERY --> COMPACT
STE --> PERMS
MAIN --> SETTINGS

The outermost layer handles process bootstrapping and fast-path routing.

Key files:

  • src/entrypoints/cli.tsx — Bootstrap entrypoint, fast-path routing
  • src/entrypoints/init.ts — Configuration, telemetry, and proxy initialization
  • src/entrypoints/mcp.ts — MCP server mode entrypoint

The CLI layer implements a fast-path pattern — special flags like --version are handled before any heavy module loading:

src/entrypoints/cli.tsx
async function main(): Promise<void> {
const args = process.argv.slice(2)
// Fast-path for --version: zero module loading needed
if (args.length === 1 && (args[0] === '--version' || args[0] === '-v')) {
console.log(`${MACRO.VERSION} (Claude Code)`)
return
}
// For all other paths, load the startup profiler
const { profileCheckpoint } = await import('../utils/startupProfiler.js')
profileCheckpoint('cli_entry')
// Route to specialized entrypoints...
}

The init() function (memoized, runs once) handles:

  1. Configuration validation and enablement
  2. Environment variable application (safe subset before trust, full after)
  3. Graceful shutdown registration
  4. OAuth account info population
  5. mTLS and proxy configuration
  6. Telemetry initialization

The main module is the largest file in the codebase (~785KB). It wires together Commander.js argument parsing, configuration loading, and the REPL launcher.

Key responsibilities:

  • Commander.js command registration with type-safe options
  • API key validation and authentication flow
  • MCP server initialization
  • Tool pool assembly
  • REPL/print mode launch
// src/main.tsx — Side effects run at import time
import { profileCheckpoint } from './utils/startupProfiler.js'
profileCheckpoint('main_tsx_entry')
import { startMdmRawRead } from './utils/settings/mdm/rawRead.js'
startMdmRawRead() // Fire MDM subprocess in parallel with imports
import { startKeychainPrefetch } from './utils/secureStorage/keychainPrefetch.js'
startKeychainPrefetch() // Fire keychain reads in parallel

The command registry manages 60+ slash commands with a sophisticated loading system:

src/commands.ts
export async function getCommands(cwd: string): Promise<Command[]> {
const allCommands = await loadAllCommands(cwd)
const dynamicSkills = getDynamicSkills()
const baseCommands = allCommands.filter(
_ => meetsAvailabilityRequirement(_) && isCommandEnabled(_),
)
// ... dedupe dynamic skills
}

Commands come from multiple sources:

  1. Built-in commands — Hard-coded in COMMANDS() (memoized)
  2. Bundled skills — Shipped with the binary
  3. Skill directory commands — User-defined in .claude/skills/
  4. Plugin commands — From installed plugins
  5. Workflow commands — From workflow scripts
  6. MCP commands — From connected MCP servers

Layer 4: Query Engine (src/QueryEngine.ts)

Section titled “Layer 4: Query Engine (src/QueryEngine.ts)”

The QueryEngine class owns the conversation lifecycle:

sequenceDiagram
participant User
participant QE as QueryEngine
participant PUI as processUserInput
participant Q as query()
participant C as claude.ts
User->>QE: submitMessage(prompt)
QE->>PUI: Process slash commands, attachments
PUI-->>QE: messages, shouldQuery, allowedTools
QE->>Q: query({ messages, systemPrompt, ... })
Q->>C: createStreamedResponse()
C-->>Q: Stream events (text, tool_use, ...)
Q->>Q: Execute tools via StreamingToolExecutor
Q-->>QE: Yield messages (assistant, user, system)
QE-->>User: SDKMessage stream

Key design: submitMessage() returns an AsyncGenerator<SDKMessage>, allowing the caller to consume messages incrementally:

src/QueryEngine.ts
export class QueryEngine {
async *submitMessage(
prompt: string | ContentBlockParam[],
options?: { uuid?: string; isMeta?: boolean },
): AsyncGenerator<SDKMessage, void, unknown> {
// 1. Process user input (slash commands, attachments)
// 2. Build system prompt
// 3. Enter query loop
for await (const message of query({ messages, systemPrompt, ... })) {
// 4. Process each message (record, track usage, yield)
}
// 5. Yield final result
}
}

The query() function implements the core agentic loop — the cycle of:

  1. Send messages to API
  2. Receive streaming response
  3. Execute tool calls
  4. Feed results back
// src/query.ts — Imports reveal the orchestration scope
import { StreamingToolExecutor } from './services/tools/StreamingToolExecutor.js'
import { runTools } from './services/tools/toolOrchestration.js'
import { isAutoCompactEnabled } from './services/compact/autoCompact.js'
import { FallbackTriggeredError } from './services/api/withRetry.js'

The loop handles:

  • Auto-compaction: Automatically shrinks context when approaching limits
  • Reactive compaction: Retries with compacted context on prompt-too-long errors
  • Model fallback: Falls back from Opus to Sonnet on repeated 529 errors
  • Tool execution: Parallel execution of concurrent-safe tools via StreamingToolExecutor

Layer 6: API Streaming (src/services/api/claude.ts)

Section titled “Layer 6: API Streaming (src/services/api/claude.ts)”

The deepest layer handles raw API communication:

// src/services/api/claude.ts — Types reveal the protocol
import type {
BetaRawMessageStreamEvent,
BetaMessageStreamParams,
} from '@anthropic-ai/sdk/resources/beta/messages/messages.mjs'
import type { Stream } from '@anthropic-ai/sdk/streaming.mjs'

This layer manages:

  • System prompt construction with cache breakpoints
  • Message normalization for the API format
  • Streaming event processing (message_start, content_block_start, content_block_delta, etc.)
  • Beta feature headers (thinking, effort, fast mode, tool search)
  • Multi-provider support (Anthropic direct, Bedrock, Vertex)

Layer 7: Tool System (src/Tool.ts, src/tools.ts)

Section titled “Layer 7: Tool System (src/Tool.ts, src/tools.ts)”

The tool system is the primary mechanism for Claude to interact with the world:

// src/Tool.ts — Core tool interface (simplified)
export type Tool<Input, Output, P> = {
name: string
inputSchema: Input // Zod schema
call(args, context, canUseTool, parentMessage, onProgress?): Promise<ToolResult<Output>>
checkPermissions(input, context): Promise<PermissionResult>
isReadOnly(input): boolean
isDestructive?(input): boolean
isConcurrencySafe(input): boolean
prompt(options): Promise<string>
// ... rendering, validation, progress tracking
}

Tools are assembled via getTools() with permission-aware filtering:

src/tools.ts
export const getTools = (permissionContext: ToolPermissionContext): Tools => {
if (isEnvTruthy(process.env.CLAUDE_CODE_SIMPLE)) {
return filterToolsByDenyRules([BashTool, FileReadTool, FileEditTool], permissionContext)
}
// Full tool pool with deny-rule filtering and REPL mode handling
}
graph LR
CLI["cli.tsx"] --> INIT["init.ts"]
CLI --> MAIN["main.tsx"]
MAIN --> SETUP["setup.ts"]
MAIN --> QE["QueryEngine.ts"]
MAIN --> CMDS["commands.ts"]
MAIN --> TOOLS_TS["tools.ts"]
QE --> QUERY["query.ts"]
QE --> PUI["processUserInput.ts"]
QUERY --> CLAUDE["claude.ts"]
QUERY --> STE["StreamingToolExecutor.ts"]
QUERY --> COMPACT["compact.ts"]
CLAUDE --> RETRY["withRetry.ts"]
CLAUDE --> ERRORS["errors.ts"]
STE --> EXEC["toolExecution.ts"]
EXEC --> PERMS["permissions.ts"]
EXEC --> HOOKS["hooks.ts"]
TOOLS_TS --> TOOL["Tool.ts"]
TOOL --> BUILTIN["30+ Tool implementations"]

The session-scoped orchestrator. One per conversation. Manages message history, file state cache, usage tracking, and permission denial tracking.

src/QueryEngine.ts (1296 lines)
├── submitMessage() — Main entry point, yields SDKMessage stream
├── interrupt() — Abort current operation
├── getMessages() — Read conversation history
└── setModel() — Change model mid-session

The universal interface for all capabilities — from reading files to spawning sub-agents. Every tool implements the same interface with buildTool() providing defaults:

src/Tool.ts (793 lines)
├── Tool<Input, Output, P> — Full tool interface
├── ToolDef<Input, Output, P> — Partial definition (for buildTool)
├── buildTool() — Fill defaults, return complete Tool
├── ToolUseContext — Runtime context passed to every tool call
└── ToolPermissionContext — Permission state for tool filtering

The slash command interface supporting three types:

src/types/command.ts
type Command =
| { type: 'local'; ... } // Synchronous, local execution
| { type: 'local-jsx'; ... } // Renders Ink UI
| { type: 'prompt'; ... } // Expands to text for the model

Multi-source permission rules with pattern matching:

src/types/permissions.ts
├── PermissionMode — 'default' | 'plan' | 'auto' | 'bypassPermissions'
├── PermissionResult — 'allow' | 'deny' | 'ask' with updatedInput
├── ToolPermissionRulesBySource — Rules keyed by setting source
└── AdditionalWorkingDirectory — Extra allowed directories
sequenceDiagram
participant U as User
participant M as main.tsx
participant QE as QueryEngine
participant Q as query.ts
participant C as claude.ts
participant R as withRetry
participant API as Anthropic API
participant STE as StreamingToolExecutor
participant T as Tool.call()
U->>M: Type message + Enter
M->>QE: submitMessage(prompt)
QE->>QE: processUserInput() → messages
QE->>Q: query({ messages, systemPrompt })
Q->>C: createStreamedResponse()
C->>R: withRetry(operation)
R->>API: POST /v1/messages (streaming)
API-->>R: SSE stream events
R-->>C: Stream events
C-->>Q: Parsed messages
alt Tool use requested
Q->>STE: addTool(block, assistantMessage)
STE->>T: call(args, context, canUseTool)
T-->>STE: ToolResult
STE-->>Q: User message with tool_result
Q->>C: Next turn with tool results
end
Q-->>QE: yield messages
QE-->>M: SDKMessage stream
M-->>U: Rendered output
src/
├── entrypoints/ # Process entry (cli.tsx, init.ts, mcp.ts)
├── bootstrap/ # Early state initialization
├── commands/ # 60+ slash commands (/help, /config, /model, ...)
├── components/ # Ink React components (UI)
├── constants/ # System prompts, betas, limits
├── context/ # React context providers
├── hooks/ # React hooks (permissions, tools, state)
├── services/
│ ├── api/ # claude.ts, withRetry.ts, errors.ts
│ ├── analytics/ # GrowthBook, telemetry
│ ├── compact/ # Auto-compaction, reactive compaction
│ ├── mcp/ # MCP client, config, server management
│ ├── tools/ # StreamingToolExecutor, toolOrchestration
│ └── ...
├── tools/ # 30+ tool implementations
│ ├── AgentTool/
│ ├── BashTool/
│ ├── FileEditTool/
│ ├── FileReadTool/
│ ├── GlobTool/
│ ├── GrepTool/
│ └── ...
├── types/ # TypeScript type definitions
├── utils/ # Utility modules
│ ├── settings/ # Configuration system
│ ├── permissions/ # Permission engine
│ ├── model/ # Model selection and routing
│ ├── plugins/ # Plugin loading
│ └── ...
├── main.tsx # Commander.js CLI framework
├── commands.ts # Command registry
├── query.ts # Agentic loop
├── QueryEngine.ts # Session lifecycle
├── Tool.ts # Tool interface
└── tools.ts # Tool pool assembly