|
| 1 | +# Part 1: The 510,000 Lines Codebase |
| 2 | + |
| 3 | +`find . -name '*.ts' | wc -l` returns 1903 files. `cloc --include-lang=TypeScript .` returns 512,664 lines. |
| 4 | + |
| 5 | +To be honest, my first reaction was to close the terminal—this thing even lags for a few seconds when opened with VS Code. But I work on AI Agents (Moonshot AI / Kimi) and use Claude Code heavily every day, so I really wanted to know what was inside. So I gritted my teeth and started reading it. |
| 6 | + |
| 7 | +This is the first part of a series, and it won't delve into any specific modules. The goal is to build a mental map for you: what is Claude Code, why was its technology stack chosen this way, where are the 510,000 lines of code distributed, and the ten recurring design patterns I summarized after reading the entire code. |
| 8 | + |
| 9 | +--- |
| 10 | + |
| 11 | +## Technology Stack: Bun + TypeScript + React |
| 12 | + |
| 13 | +Yes, you read that right. A terminal CLI tool uses React. |
| 14 | + |
| 15 | +Using React for the TUI layer isn't new; Ink has been around since 2017, and it's used in Gatsby CLI and Prisma CLI. |
| 16 | + |
| 17 | +However, Claude Code's scenario is significantly more complex than a typical CLI: multiple agents running in parallel, streaming output, user interruptions during tool execution, and permission pop-ups. At this level of complexity in state management, using React is indeed more reasonable than manual development. |
| 18 | + |
| 19 | +``` |
| 20 | +Technology Choices: |
| 21 | +
|
| 22 | + Bun → Fast startup (4x faster than Node.js), native TypeScript support |
| 23 | + TypeScript → 510,000 lines without a type system is practically suicide |
| 24 | + React + Ink → Declarative terminal UI, complex state management |
| 25 | + Commander.js → CLI parameter parsing (most mature in the Node ecosystem) |
| 26 | + Zod → Runtime data validation + automatic JSON schema generation |
| 27 | + ripgrep → Search engine written in Rust (GrepTool directly calls binary code) |
| 28 | + GrowthBook → Remote feature flags and A/B testing |
| 29 | +
|
| 30 | +``` |
| 31 | + |
| 32 | +TypeScript is a self-explanatory choice. Without a type system, changing a single function signature in 510,000 lines of code would cause countless system crashes. Zod is a clever choice too — it performs both runtime data validation and type inference, essentially using one schema to do two things: tell the TypeScript compiler "what the input looks like," and intercept invalid input at runtime. |
| 33 | + |
| 34 | +--- |
| 35 | + |
| 36 | +## Directory Structure: Four Layers |
| 37 | + |
| 38 | +After two days of painstakingly studying Claude Code, I think it can be understood by dividing it into four layers. This isn't an official division, but rather my own mental model after reading it. |
| 39 | + |
| 40 | +### First Layer: Entry Point and UI |
| 41 | + |
| 42 | +This is the layer with things users can directly see and touch. Everything is distributed across: |
| 43 | + |
| 44 | +``` |
| 45 | + src/screens/ → React pages (REPL main interface, Onboarding, Doctor diagnostics) |
| 46 | + src/components/ → UI components (permission pop-ups, tool execution progress, diff preview) |
| 47 | + src/commands/ → Forward-slash commands (/commit, /compact, /model, /review) |
| 48 | +``` |
| 49 | + |
| 50 | +`src/screens/REPL.tsx` is the main interface, over 3000 lines long, essentially the "shell" of the entire application. It handles input capture, message rendering, and command dispatch. Everything you see in the terminal originates from here. |
| 51 | + |
| 52 | +### Second Layer: The Engine |
| 53 | + |
| 54 | +The engine layer contains two files that can be thought of as Claude Code's brain. |
| 55 | + |
| 56 | +``` |
| 57 | +src/query.ts → Core agentic loop (line 1729), while(true) loop |
| 58 | +src/QueryEngine.ts → Session state manager (line 1295), one instance per conversation |
| 59 | +``` |
| 60 | +`QueryEngine` is the outer "container", managing state across rounds (message history, token usage, file cache). `query.ts` is the inner "engine," triggering `queryLoop()` once per user input. The latter contains the `while(true)` loop that calls the LLM, executes tools, handles errors, and continues until the LLM provides a plain text response. |
| 61 | + |
| 62 | +[Part two of this series](02-agent-loop_EN.md) will delve deeper into `query.ts`. |
| 63 | + |
| 64 | +### Third Layer: Tools |
| 65 | + |
| 66 | +Claude Code has 30+ tools that can be seen as all capabilities that the LLM can call. A non-exhaustive list is: |
| 67 | + |
| 68 | +``` |
| 69 | +src/tools/BashTool/ → Line 1143, the most complex single tool |
| 70 | +src/tools/AgentTool/ → Line 1397 (total 6700 lines in the directory), sub-agent generation |
| 71 | +src/tools/FileEditTool/ → Search, replace, and edit |
| 72 | +src/tools/FileReadTool/ → File reading |
| 73 | +src/tools/GrepTool/ → Ripgrep wrapper |
| 74 | +src/tools/GlobTool/ → File pattern matching |
| 75 | +src/tools/WebFetchTool/ → Web page content fetching |
| 76 | +src/tools/WebSearchTool/ → Web page search |
| 77 | +src/tools/NotebookEditTool/ → Jupyter Notebook editing |
| 78 | +src/tools/LSPTool/ → Language server protocol interaction |
| 79 | +
|
| 80 | +...and a dozen more |
| 81 | +``` |
| 82 | +Each tool is completely self-contained: schema definition, permission checks, execution logic, UI rendering, and context compression summarization are all in one directory. There's no global registry, no base class inheritance; everything is a pure object generated by the `buildTool()` factory function. This will be discussed in detail in Part 3. |
| 83 | + |
| 84 | +### Fourth Layer: Infrastructure |
| 85 | + |
| 86 | +Infrastructure is the underlying system supporting the operation of the above three layers. |
| 87 | + |
| 88 | +``` |
| 89 | +src/services/ → API client, MCP protocol, OAuth, caching |
| 90 | +src/utils/ → Permission system, Feature Flag, model configuration, event tracking |
| 91 | +src/memdir/ → Memory system (cross-session persistence) |
| 92 | +src/skills/ → Skill system (reusable task templates) |
| 93 | +src/buddy/ → Pet system (unreleased, but the code is all there) |
| 94 | +src/voice/ → Voice mode (codename Amber Quartz) |
| 95 | +src/bridge/ → Remote control mode (31 files) |
| 96 | +src/coordinator/ → Multi-Agent orchestration |
| 97 | +``` |
| 98 | + |
| 99 | +The permission system deserves a separate mention: Claude Code has five permission levels, from "fully automatic" to "always ask". Tools calls go through two stages: first, `validateInput()` checks the input's validity (if invalid, it comes back to the LLM querying for a new input without a pop-up); then, `checkPermissions()` checks permissions (possibly prompting the user for confirmation). This two-phase design is clever — most rejected operations are due to invalid input, not insufficient permissions, and the two-phase design greatly reduces pop-up interruptions. |
| 100 | + |
| 101 | +--- |
| 102 | + |
| 103 | +## Ten Design Philosophies |
| 104 | + |
| 105 | +After reading 500,000 lines of code, some patterns recurred. I've tried to summarize them into ten, which will be cited in each subsequent article. |
| 106 | + |
| 107 | +**1. Externalize State.** All external dependencies of `QueryEngine` are injected through `QueryEngineConfig` (20+ fields), not imported internally. This means you can pass a mock list of tools and API clients into unit tests without starting the entire application. |
| 108 | + |
| 109 | +**2. Incremental Complexity.** Keep simple things simple. `FileReadTool` has only a few dozen lines; `BashTool` has 1143 lines. Not every tool needs the same complexity; complexity should be concentrated where it's needed. |
| 110 | + |
| 111 | +**3. Feature Flag Gating.** 44 compile-time/runtime flags. A feature failed internal validation? It's deleted at compile time, leaving no string. This isn't a "if (false)" style fake deletion; it's a physical deletion via DCE (Dead Code Elimination) in Bundle. |
| 112 | + |
| 113 | +**4. Type as Documentation.** The Zod schema performs both validation and type inference. The field names in the `ToolDef` type are the most accurate interface documentation. Newcomers can understand which methods a tool needs to implement just by looking at the type definition. |
| 114 | + |
| 115 | +**5. Context Economics.** Tokens are like money. Tool output exceeding a threshold is written to disk, but only a digest is left in the context. The context has four layers of compression. The `getToolUseSummary()` method defines the digest strategy for each tool during compression. |
| 116 | + |
| 117 | +**6. Security Layering.** Two-phase gating (authentication + permissions), five-level permission mode, BashTool's command classifier (parses commands into search/read/write categories to match permission rules), macOS sandbox-exec / Linux seccomp sandbox. Security isn't a single wall, it's multiple walls. |
| 118 | + |
| 119 | +**7. Graceful Degradation.** API timeout retries (exponential backoff, maximum 5 times). If the model doesn't support `stream_options`, remove that parameter and retry. Sub-Agent crashes don't affect the main loop. Output hitting `max_output_tokens` limits automatic retries to a maximum of 3 times. |
| 120 | + |
| 121 | +**8. Declarative Configuration.** Tools, commands, Skills, and Agent types are all declarative. A Skill is simply a `.md` file with a YAML frontmatter. An Agent type is a configuration object. Assembled on demand at runtime, no registration required in the code. |
| 122 | + |
| 123 | +**9. Streaming Everything.** **API response streaming:** StreamingToolExecutor begins executing the tool while the model is still outputting, pushing progress information to the terminal in real time. Users always see progress, instead of waiting for a long task to complete. |
| 124 | + |
| 125 | +**10. Experimental Progress:** Ablative testing infrastructure (`ABLATION_BASELINE` allows for one-click shutdown of thinking/compact/memory/background), A/B testing flags, and the ability to run controlled experiments to quantify the value of each feature before launch. It's not about "launching because it feels useful," but about "launching only when the data says it's useful." |
| 126 | + |
| 127 | +--- |
| 128 | + |
| 129 | +## About this series |
| 130 | + |
| 131 | +The following six articles will delve deeper into each of these: |
| 132 | + |
| 133 | +- Article 2: The while(true) loop on line 1729 of `query.ts` |
| 134 | +- Article 3: The design of the tool system and search, replace, and edit |
| 135 | +- Article 4: Four-layer context compression |
| 136 | +- Article 5: Parallelism of streaming tools in StreamingToolExecutor |
| 137 | +- Article 6: Multi-Agent Collaboration System |
| 138 | +- Article 7: Unreleased features behind Feature Flag |
| 139 | + |
| 140 | +I also replicated these core design patterns in 1300 lines of Python: [CoreCoder](https://github.com/he-yufeng/CoreCoder). Each article will explain the source code and the CoreCoder implementation. |
| 141 | + |
| 142 | +--- |
| 143 | + |
| 144 | +> This article is the first in the [Claude Code Source Code Guide](00-index.md) series. |
0 commit comments