Inside Claude Code
Architecture breakdown from the leaked source
512K lines of TypeScript. 144 React components rendered via Ink in a terminal. 43 tools behind permission gates. 86 slash commands. 16 bundled skills. 85 hooks. This is what's inside.
RuntimeBunUIReact + Ink (terminal renderer) — 144 components in src/components/CLICommander.js with preAction hooks for initializationStatesrc/state/ — AppStateStore.ts with selectors, change handlers, no external libraryEntrysrc/main.tsx → Commander parse → preAction init → React/Ink REPLEnginesrc/QueryEngine.ts — async generator, drives the agentic loopBridgesrc/bridge/ — 32 files for IDE communication (VS Code, JetBrains). JWT auth, session management, bidirectional messagingStartup is a race against the 135ms import window. Three prefetches fire before any module loads:
startMdmRawRead()Spawns subprocess for managed device settings — completes in ~80msstartKeychainPrefetch()macOS keychain read — 50-180ms, already done by the time CLI parsescaptureEarlyInput()Buffers keystrokes during boot so nothing is lostinit()Loads settings, establishes trust dialog, loads remote managed settingsDeferredAfter first render: initUser(), getUserContext(), getRelevantTips(), countFilesRoundedRg() (3s timeout), prefetchOfficialMcpUrls(), refreshModelCapabilities()System prompt is layered. Each function uses memoize() — computed once, cached for the session. Calling setSystemPromptInjection() clears both caches:
Base promptBehavior rules, constraints, output format, tool descriptionsgetSystemContext()git status --short (truncated at 2000 chars), git log --oneline -n 5, current + main branch names, git user name. Includes disclaimer: "snapshot in time, will not update during conversation"getUserContext()getMemoryFiles() discovers all CLAUDE.md files. Disabled by CLAUDE_CODE_DISABLE_CLAUDE_MDS env var or --bare mode. Returns claudeMd content + ISO date.Memory systemsrc/memdir/ — 8 files: findRelevantMemories.ts, memoryScan.ts, memoryAge.ts (staleness tracking), memoryTypes.ts, team-level paths and promptsQueryEngine.submitMessage() is an async generator yielding typed SDK messages. It calls query() (from src/query/) which manages the multi-turn loop. The query pipeline has 5 files: config.ts, deps.ts, stopHooks.ts, tokenBudget.ts, transitions.ts.
1. API callMessages + system prompt + tool definitions → Claude API2. StreamResponse arrives. Content blocks yielded as they stream.3. textIf just text → render to terminal. Yield success result.4. tool_useIf tool_use blocks → wrappedCanUseTool() checks permission → execute → result becomes user message5. LoopGo to step 1 with updated messagesTerminal states: success, error_max_turns, error_max_budget_usd, error_max_structured_output_retries (limit: 5), error_during_execution. Each includes duration_ms, usage, modelUsage, permission_denials, fast_mode_state.
Each tool is a directory under src/tools/ with implementation, UI component, prompt, and types. The BashTool alone has 18 files:
bashSecurity.ts runs 20+ numbered validators on every command. shouldUseSandbox.ts determines sandboxing. commandSemantics.ts analyzes command intent. sedValidation.ts + sedEditParser.ts specifically validate sed commands. destructiveCommandWarning.ts warns before dangerous ops.
Only early-allow bypass: safe heredoc patterns with single-quoted delimiters that are provably non-expanding. Everything else goes through the full chain. Quote-aware analysis prevents ANSI-C and locale quoting escapes.
FileEditTool/utils.ts implements a multi-step matching pipeline for old_string. Edits don't just do string replace — they handle quote normalization and API artifact desanitization:
findActualString()Tries exact match → curly-to-straight quote normalization → API desanitization. Returns first match.applyEditToFile()replaceAll() if replace_all flag, else replace(). Empty newString strips trailing newline.getPatchForEdits()Validates no new_string contains a future old_string (prevents conflicts). Applies sequentially, generates unified diff hunks.AgentTool.tsx supports three spawn modes. Each agent gets isolated context via assembleToolPool(). Flat roster — teammates cannot spawn other teammates.
SyncBlocks parent. Iterates agentMessages[] until completion. finalizeAgentTool() returns result.AsyncregisterAsyncAgent() detaches. void runWithAgentContext() runs in background. Returns outputFile path. enqueueAgentNotification() on complete.TeamspawnTeammate() with tmux splitpane. use_splitpane: true. Flat roster enforced.BackgroundingForeground agents can be backgrounded mid-execution — async closure takes over ownershipWorktreecreateAgentWorktree(slug) creates temporary git worktree. Auto-cleaned if no changes.Each command is a directory under src/commands/. Here's every one:
Skills are composable prompt+tool packages loaded from src/skills/bundled/. Custom skills loaded via loadSkillsDir.ts. MCP skills built via mcpSkillBuilders.ts.
23 files in src/services/mcp/. External tools are MCP servers — not built-in features.
TransportInProcessTransport.ts, SdkControlTransport.tsConnectionMCPConnectionManager.tsx, client.ts, useManageMCPConnections.ts hookAuthauth.ts, xaa.ts, xaaIdpLogin.ts, oauthPort.ts, channelAllowlist.ts, channelPermissions.tsDiscoveryToolSearch — lazy loading. Tools discovered at runtime via officialRegistry.ts (prefetched after first render)ApprovalmcpServerApproval.tsx — servers require user consent before activationcost-tracker.ts uses OpenTelemetry-style counters:
TrackedInput tokens, output tokens, cache read/creation tokens, USD cost, API duration, wall-clock duration, tool exec time, lines added/removedCalculationcalculateUSDCost() per model. Below $0.50: full precision. Above: rounded to cents.PersistencerestoreCostStateForSession() — checks session ID match before restoring. Prevents duplication.Nested costsAdvisor tool usage recursively tracked: addToTotalSessionCost(advisorCost, advisorUsage, model)The entire application is hook-driven. Key groupings from src/hooks/:
Based on analysis of github.com/nirholas/claude-code — the leaked Claude Code source.