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