The full conversation log from a weekend-long first-principles investigation of two leaked Claude Code source trees — v0.2.8 and v2.1.88
I have the extracted source of Claude Code v0.2.8. Walk me through the end-to-end architecture, from when a user types a command to when they see the response. Start from the top.
Claude Code is a TypeScript terminal application built with Ink (React for the terminal), the Anthropic SDK, Commander.js for CLI parsing, and Zod for input validation. Internal codename: "Tengu" — visible in all analytics events like tengu_init, tengu_api_query.
The end-to-end flow:
15 tools total. Read-only tools run concurrently (up to 10). Write tools run serially. Everything gates through the permission system: always-allowed (reads), session-scoped (file writes), persistent (bash commands).
How is the system prompt assembled? Is prompts.ts appended to some original Claude system prompt, or is it the whole thing?
It's the whole thing. The system field in the Messages API is the entire system-level instruction. There is no hidden base prompt server-side. What prompts.ts produces is all Claude sees.
| Block | Source |
|---|---|
| "You are Claude Code..." | getCLISyspromptPrefix() |
| Main instructions (tool policy, tone, tasks) | getSystemPrompt()[0] |
| Environment info | getSystemPrompt()[1] |
| Safety guardrails (repeated) | getSystemPrompt()[2] |
<context name="gitStatus"> | getContext() |
<context name="claudeFiles"> | same |
<context name="readme"> | same |
Why are the safety guardrails repeated at the beginning AND end of the prompt?
Two reasons:
<context> blocks that follow contain user-controlled content (README, CLAUDE.md, git status). The second guardrail is the last thing before untrusted content enters the prompt. Defense against prompt injection in repo files.This is best-effort. The real defense-in-depth is: prompt guardrails + permission system + Zod validation + command injection detection + file path restrictions + Docker-only bypass mode.
There's a comment in query.ts that reads like a wizard's rules, something about thinking constraints. What does it actually mean? And why are there two separate mechanisms for thinking?
The "wizard comment" documents three API constraints the developer learned the hard way:
max_thinking_length > 0.And the two mechanisms are mutually exclusive by design:
| Extended Thinking | ThinkTool | |
|---|---|---|
| Where | Server-side, before response | Client-side, mid-response tool call |
| Gating | USER_TYPE === 'ant' only | Statsig feature gate |
| Cost | Billed as output tokens | Zero — returns a fixed string |
| Trigger | "think" (4K), "think hard" (10K), "ultrathink" (32K) | Model decides to call it |
If ThinkTool is enabled, maxThinkingTokens returns 0. Extended thinking wasn't available to external users at launch. ThinkTool was the externally-shippable workaround — a feature flag migration pattern: ship the tool-based workaround, gate the native feature, deprecate the workaround once native goes GA.
So what actually happened? Did they deprecate ThinkTool?
Yes. ThinkTool removed entirely in January 2026. Extended thinking went GA, then adaptive (thinking: {type: "adaptive"}) in February 2026. "Ultrathink" was deprecated, then re-introduced in March after users complained about losing the ability to force deep reasoning. The team learned that user agency over reasoning depth matters, even with adaptive mode.
The full arc: tool workaround → API feature (gated) → GA → fixed budget → adaptive → user-controlled effort levels. Simultaneously more automatic and more user-controllable.
How does the AgentTool actually spawn sub-agents? Does it launch a separate process?
No separate process. It calls the same query() function recursively within the same Node.js process:
[UserMessage(prompt)]getAgentPrompt() — ~15 lines vs ~120)The parent sees only the final text. The entire agent trajectory is compressed into a single tool_result. And because isReadOnly() = true, multiple agents run concurrently via the same pool used for read-only tools.
That comment — "No recursive agents, yet.." — suggested they'd eventually allow nesting. Instead, in v2.1.88, they went a different direction entirely: flat teams over deep trees.
Now compare everything we found in v0.2.8 against the current Claude Code. What changed across the five areas we audited? What stayed the same? And what does each change reveal about what the team learned?
ThinkTool removed. ArchitectTool replaced by EnterPlanMode/ExitPlanMode — planning promoted from a tool to a workflow. Memory tools replaced by auto memory. WebFetch and WebSearch added. Every addition reflects a bottleneck observed in real usage.
One dispatch_agent became five built-in types (Explore on Haiku, Plan on main model, general-purpose with full tool access), custom agents via .claude/agents/, and agent teams with inter-agent messaging. No recursion — flat teams over deep trees.
Six modes: default, acceptEdits, plan, auto, dontAsk, bypassPermissions. Plus 28 hook events and auto mode — a second AI model reviewing each tool call. AI supervising AI.
ThinkTool removed. Native thinking went GA, then adaptive. "Ultrathink" deprecated then un-deprecated. User agency over reasoning depth matters.
3 prompt strings became 110+ assembled dynamically. Context 200K → 1M. CLAUDE.md survives compaction. Auto memory replaced explicit tools.
What's the deepest pattern across all of this?
Every change increases autonomy while simultaneously increasing controllability. Auto mode makes the agent more autonomous. Hooks, rules, managed settings make it more controllable. The team is solving for "maximum useful autonomy within user-defined boundaries" — the central problem of AI deployment.
I keep seeing references to VCR, binary feedback, Statsig, MCP. What are all these supporting systems around the core loop?
VCR — "Video Cassette Recorder." Deterministic test fixtures. SHA-1 hashes the dehydrated message history, replays cached responses. Solves the fundamental testing problem: API responses are non-deterministic.
Binary feedback — Ant-only RLHF. Two API calls in parallel, user picks the preferred response. This is why temperature = 1 — not for creativity, but to maximize variation between A/B calls.
MCP — Server configs loaded at startup, tools wrapped as standard Tool objects. MCP tools look identical to built-in tools from the model's perspective.
REPL — Not a readline loop. A React (Ink) component with a state machine managing messages, permissions, binary feedback, and input modes.
How was the v0.2.8 source actually leaked? Can we trace the provenance?
The bundled cli.mjs on npm contained an inline base64 source map. The full TypeScript source embedded as a data URL at the bottom of the compiled JavaScript. Discovered on February 24, 2025 — launch day.
A missing .npmignore or misconfigured files field in package.json. The extraction is trivial: decode the base64, parse the JSON, read the sourcesContent array. Every original TypeScript file, with comments intact.
THIS IS the exact leakage I need to investigate. Search for the sources, build the plan, update the KNOWLEDGE.md file.
Found and cloned Exhen/claude-code-2.1.88. The scale difference is staggering:
| v0.2.8 | v2.1.88 | |
|---|---|---|
| Files | 211 | 1,902 |
| Lines of TypeScript | 26,000 | 514,587 |
Top-level src/ dirs | 9 | 35 |
| Tool directories | 16 | 40 |
Discoveries that stopped me cold:
anti_distillation: ['fake_tools'] injected into API body to prevent competitor model training on outputsPush the cloned directory into my private GitHub repo. Then download claw-code and tell me, why does this exist?
Push failed initially — shallow clone had graft points where parent objects weren't downloaded. Fixed by reinitializing the git history from scratch.
claw-code is not a copy of Claude Code. It's a clean-room Python + Rust rewrite. Different language, different legal status, different purpose.
Why: DMCA-proof alternative. The TypeScript leaks are legally risky. A clean-room rewrite in a different language establishes independent creation. Also a demo of autonomous agent capability — 48K lines of systems Rust in 72 hours.
Design the curriculum to understand how Claude Code works, starting from first principles and into the deep dive of deliberate choices made by Anthropic.
Seven modules, each with ordered reading lists and the design belief it reveals:
query.ts → QueryEngine.ts. The recursive async generator.prompts.ts → buildEffectiveSystemPrompt(). 3 strings → 110+ dynamic.ToolSearchTool.Explain the recursive async generator as a mechanism. Use a file system analogy. I want to build the intuition from first principles.
Three building blocks:
yield — suspend and hand a value to the caller. The caller controls pacing (backpressure).yield* — delegate to another generator. Pipes — does not collect.async — each await pauses for I/O without blocking.query.ts| File traversal | Agentic loop |
|---|---|
getAllFiles(dir) | query(messages, tools) |
readdir(dir) | callClaudeAPI() |
yield file | yield streamEvent |
isDirectory? | stop_reason === 'tool_use'? |
yield* getAllFiles(sub) | yield* query([...msgs, toolResults]) |
| function ends | stop_reason === 'end_turn' |
Three properties gained: streaming (first token in ~300ms), composability (AgentTool.call() does yield* query(agentMessages) — sub-agent events pipe into the parent with no special casing), and backpressure (Ctrl+C breaks the generator mid-execution, no wasted API calls).
How does prompt caching work differently before and after the DYNAMIC_BOUNDARY marker?
SYSTEM_PROMPT_DYNAMIC_BOUNDARY = '__SYSTEM_PROMPT_DYNAMIC_BOUNDARY__'
Before every API call, splitSysPromptPrefix() splits the system prompt into 4 blocks:
scope: 'global' = cross-org cache. The block is identical for every Claude Code user worldwide. Cached once, shared globally. Everything after — environment info, memory, CLAUDE.md, git status — changes per user, per session.
| Scenario | Static blocks | Dynamic blocks |
|---|---|---|
| Normal (first-party, no MCP) | global | uncached |
| MCP tools present | org | org |
| 3P providers (Bedrock/Vertex) | org | org |
MCP tools break global caching because schemas are per-user. Source comment: "IMPORTANT: Do not add any more blocks for caching or you will get a 400."
The boundary marker is load-bearing: moving content before it silently corrupts other users' caches.
What are all the categories of system prompts and how are they prioritized? How do they interact differently?
Six categories, strict priority. First match wins:
The non-obvious case: proactive/KAIROS mode. Normal agents replace the default entirely. But when isProactiveActive() is true, agent instructions append to the default. The daemon already has the right base identity — the agent definition adds domain-specific behavior on top.
Two axes: identity (what kind of agent? winner-takes-all) and constraint (what additional rules? always composable via appendSystemPrompt). Override dropping append is intentional: loop mode needs total determinism.
A non-React cron scheduler core shared by the REPL and Agent SDK. Polls scheduled_tasks.json every second. Lock-based ownership — only one Claude session per project fires tasks. Non-owners probe every 5 seconds to take over if the owner crashes.
:00 load spike without restarting clientsmax(3, longestBacktickRun + 1). Prevents a malicious task prompt from escaping the fence. Requires user confirmation before execution.Triple gate (cheapest first): time ≥24h → sessions ≥5 → lock. Fires a forked subagent with bash restricted to read-only commands. Reviews past transcripts, improves memory files. User can kill from the bg-tasks dialog — lock rollback handles cleanup.
/ultraplan creates a Cloud Code Runner session, runs Opus in plan mode with a browser UI. The local repo is teleported via a git bundle:
git bundle create --all — full history (100MB max)git bundle create HEAD — current branch onlygit commit-tree HEAD^{tree} — single parentless commit, just the snapshotThe teleport sentinel — finally answered:
ULTRAPLAN_TELEPORT_SENTINEL = '__ULTRAPLAN_TELEPORT_LOCAL__'
When the user clicks "teleport back to terminal" in the browser, it sends an is_error: true tool_result with the sentinel on the first line, plan text on the next. Two exit paths: teleport (plan comes back to terminal, remote archived) or remote (CCR executes, delivers a PR). The ExitPlanModeScanner is a pure stateful classifier with no I/O — feed it recorded events for unit tests and offline replay.
Log the entire conversation history to KNOWLEDGE.md and draft a blog article comparing the two versions. Trace the minds of the creators of Claude Code.
Updated KNOWLEDGE.md — now 1,800+ lines across 14 sessions. Seven design beliefs extracted from the code:
Core thesis:
An agentic coding tool should be a reliable system, not a capable demo. v0.2.8 was the capable demo. v2.1.88 is the beginning of the system.
Publish it.
Published to twyoon.com.
14 sessions. Two source trees. 540,000 lines of TypeScript. Seven beliefs. One conclusion: the code is the artifact. The thinking is the product.