# How Claude Code Works — Deep Dive: The Fantastic Voyage of a Prompt

> **This lecture is based on the actual `claude-code` source (a snapshot from around March 2026).**
> **Intended reader**: a beginner in computer science — you do not need to write code; you only need to know that "a program is instructions executed one step at a time."
> **How to read**: the text contains many mermaid diagrams; use a reader that renders mermaid (VS Code + the Markdown Preview Mermaid Support plugin, Typora, Obsidian). Every important claim is tagged `file:line`, so you can follow the trail into `./claude-code/src/`.
>
> Compared with the primer, every chapter here goes into real source: quoted snippets with line-by-line commentary, the engineering trade-off of "why this design," a diagram for each mechanism, and multiple examples of different fates for each class of scene.

---

> 🗺️ **Companion architecture diagram**: this project has a dedicated figure in the [**seven-agent architecture gallery**](架构图库.html#ch2) — **the full timing of one turn (permission is a cross-cutting gate; context is reassembled every turn)**.
> Every figure in the gallery first states "what question it answers" and "the load-bearing thesis," and has been through three rounds of review and render acceptance.

## Contents

1. [Start with the idea: what is an AI agent?](#1-start-with-the-idea-what-is-an-ai-agent)
2. [The map: five layers and the data flow of one request](#2-the-map-five-layers-and-the-data-flow-of-one-request)
3. [The journey begins: what happens at startup](#3-the-journey-begins-what-happens-at-startup)
4. [The moment you press Enter: how input is routed](#4-the-moment-you-press-enter-how-input-is-routed)
5. [Packing the suitcase: how a request to the model is assembled](#5-packing-the-suitcase-how-a-request-to-the-model-is-assembled)
6. [The heart: the agent loop (the most important chapter)](#6-the-heart-the-agent-loop-the-most-important-chapter)
7. [The model's "hands": the tool system and the permission gate](#7-the-models-hands-the-tool-system-and-the-permission-gate)
8. [A field guide to the tools: the model's 40 weapons, one by one](#8-a-field-guide-to-the-tools-the-models-40-weapons-one-by-one)
9. [A field guide to slash commands: how 60+ shortcuts work](#9-a-field-guide-to-slash-commands-how-60-shortcuts-work)
10. [Ecosystem components: what plugins, skills, and MCP each are](#10-ecosystem-components-what-plugins-skills-and-mcp-each-are)
11. [Feature teardown: effort, login, model switching, …](#11-feature-teardown-effort-login-model-switching)
12. [Memory and forgetting: context compaction](#12-memory-and-forgetting-context-compaction)
13. [Clones and the emergency brake: sub-agents and interrupt](#13-clones-and-the-emergency-brake-sub-agents-and-interrupt)
14. [The full picture: one diagram of the whole path](#14-the-full-picture-one-diagram-of-the-whole-path)
15. [Appendix: source map](#appendix-source-map)

---

## 1. Start with the idea: what is an AI agent?

### 1.1 The model itself can only "talk" — and it talks with no memory

Start with two common misconceptions.

**Misconception 1: AI is a smart program that can do anything.** In fact a large language model (LLM) does exactly one thing: it reads a stretch of text, then continues writing text. Picture a super-erudite person locked in a room — they have read every book in the world, but they have no hands, no eyes, no telephone; the only way you communicate is by sliding a note under the door.

**Misconception 2: AI remembers what you said.** In fact the model has **no memory at all**. Every time you send a message, the software has to resend the **entire chat log from the first sentence**. "It remembers" is the software remembering for it. That point will come up again and again — it is the key to understanding "why longer sessions get slower and more expensive" and "why we need compaction."

Both misconceptions point at the same conclusion: a model by itself cannot "edit a source file" for you — it can only tell you "you should change line 3 to xxx." The act of changing the file is something it cannot do.

### 1.2 Agent = brain + hands + a loop

An **agent** is the software system that gives that "person in the room" hands and eyes:

| Metaphor | What it is in Claude Code | Source location |
|---|---|---|
| 🧠 Brain | The Claude model (remote API call) | `src/services/api/claude.ts` |
| ✋ Hands | **Tools**: read/write files, run commands, search | `src/tools/` (about 40) |
| 👀 Eyes | The results those tools return (`tool_result`) | `src/services/tools/toolExecution.ts` |
| 🔁 Nervous system | **The main loop**: feed results back to the brain so it can decide the next step | `queryLoop` in `src/query.ts` |
| 🛡️ Guard | The permission system: dangerous actions need your consent first | `src/utils/permissions/` |

The core idea of an agent fits in one sentence:

> **The model talks → the software acts for it → the result of that action is told back to the model → the model talks again → until the job is done.**

### 1.3 What "the model wants a tool" actually looks like in the data

This is the place beginners most often find "mystical." We take it apart with data. The model does not actually pick up a phone; it generates a special structure in the reply stream, called a **`tool_use` block**, roughly:

```json
{
  "type": "tool_use",        // 块的类型：这是一次工具调用
  "id": "toolu_01ABC...",    // 这次调用的唯一编号（后面配对要用）
  "name": "Bash",            // 要用哪个工具
  "input": { "command": "ls src" }  // 给工具的参数
}
```

When the software receives that block, it knows "the model wants to act," and acts for it. The result is wrapped in another special structure (a **`tool_result` block**) and sent back:

```json
{
  "type": "tool_result",
  "tool_use_id": "toolu_01ABC...",   // 注意：用 id 配对，标明这是哪次调用的结果
  "content": "a.ts\nb.ts\nc.ts"      // 真实的命令输出
}
```

The whole agent loop, said plainly, is the software acting as "translator + runner": **turn the model's textual intent into a real operation, then turn the result of that operation back into text.** And that `id` pairing mechanism is the source of a key design in chapter 13 ("orphan tool results must be patched with a fake reply").

### 1.4 A minimal example

Suppose you say: "*look at the files in `src`, then tell me which file is largest*." Inside the software the conversation looks like this:

```mermaid
sequenceDiagram
    participant 你
    participant 软件 as Claude Code 软件
    participant 模型 as Claude 大模型

    你->>软件: 看看 src 里有什么文件，哪个最大？
    软件->>模型: 【打包】员工手册+git状态+你的问题+工具清单
    模型-->>软件: tool_use: Bash("ls src")
    软件->>软件: 真的去执行 ls src（动手！）
    软件->>模型: tool_result: a.ts b.ts c.ts
    模型-->>软件: tool_use: Bash("du -h src/*")
    软件->>软件: 执行 du -h src/*
    软件->>模型: tool_result: a.ts 2K, b.ts 8K, c.ts 1K
    模型-->>软件: 纯文字（无 tool_use）：b.ts 最大，8KB
    软件->>你: 📄 b.ts 最大（8KB）
```

Watch: the model speaks three times. The first two times are `tool_use`; the third has no `tool_use` — **"no tool call" is the loop's stop signal**. Everything else is there to make the loop safer, faster, and cheaper in tokens.

---

## 2. The map: five layers and the data flow of one request

Before the details, a map. Claude Code, outside in, is five layers:

```mermaid
flowchart TB
    subgraph L1["🖥️ 第 1 层：界面层（终端 UI）"]
        UI["输入框 / 文本渲染 / 权限弹窗<br/><i>src/components · src/screens/REPL.tsx · src/ink</i>"]
    end
    subgraph L2["🚦 第 2 层：输入分流层"]
        ROUTER["判断输入是斜杠命令 / !shell / 普通提问<br/><i>src/utils/handlePromptSubmit.ts · processUserInput.ts</i>"]
    end
    subgraph L3["📦 第 3 层：上下文组装层"]
        CTX["系统提示词 + CLAUDE.md + git 状态<br/><i>src/constants/prompts.ts · src/context.ts · src/utils/api.ts</i>"]
    end
    subgraph L4["🔁 第 4 层：Agent 主循环（心脏）"]
        LOOP["请求模型 → 执行工具 → 追加结果 → 再请求<br/><i>src/query.ts · src/services/tools/</i>"]
    end
    subgraph L5["🌐 第 5 层：API 通信层"]
        API["流式 HTTP / SSE 解析 / 重试 / 中断<br/><i>src/services/api/claude.ts</i>"]
    end

    UI --> ROUTER --> CTX --> LOOP --> API
    LOOP -.->|调用| TOOLS["🔧 工具箱：Bash / Read / Write / Edit / Grep ...<br/><i>src/tools/</i>"]
    API -.->|HTTPS| CLAUDE["☁️ Anthropic 服务器上的 Claude 模型"]
```

- **Layer 1 (UI)**: the input box you see in the terminal, the typing effect, the "allow this command?" dialog — all rendered in the terminal with React (a heavily modified Ink library lives in `src/ink/`). Writing a terminal UI in React is an interesting choice: developers can reuse mature component thinking to manage complex UI state.
- **Layer 2 (routing)**: after you press Enter, the software first decides whether the text is for the model, or a local command. This layer's rules are surprisingly naive (chapter 4).
- **Layer 3 (packing)**: once it is going to the model, pack the system prompt, memory files, git status, and other "background material." This layer's core tension is "more information for the model is better" versus "tokens are expensive and slow" (chapter 5).
- **Layer 4 (the loop)**: the real heart. The most precise engineering in the whole product is piled here (chapters 6, 12, 13).
- **Layer 5 (transport)**: talk to Anthropic's servers. The keyword is "streaming" — the server writes a bit and sends a bit, which is why you see the answer "typed" one character at a time.

For one complete request, data flows across the layers like this:

```mermaid
sequenceDiagram
    participant UI as ① 界面层
    participant RT as ② 分流层
    participant CX as ③ 组装层
    participant LP as ④ 主循环
    participant API as ⑤ 通信层
    participant CL as ☁️ 模型

    UI->>RT: 回车，交出一整段文本
    RT->>RT: 空?exit?!开头?/开头?
    RT->>CX: 判定"发给 AI"
    CX->>CX: 取缓存的系统提示词/CLAUDE.md/git状态
    CX->>LP: 完整消息数组 + 工具清单
    loop 主循环（可能转很多圈）
        LP->>API: 流式请求
        API->>CL: HTTPS (stream: true)
        CL-->>API: SSE 事件流（逐字/逐块）
        API-->>LP: 组装好的 text / tool_use 块
        LP->>LP: 有 tool_use 就执行工具，结果追加进历史
    end
    LP-->>UI: 最终答案（无 tool_use 的那次回复）
```

Next we follow one prompt from birth to answer, layer by layer.

---

## 3. The journey begins: what happens at startup

Between typing `claude` in the terminal and seeing the input box, the software quietly does a lot. First the full flow:

```mermaid
flowchart LR
    A["敲下 claude"] --> B["分拣员 cli.tsx<br/>特殊参数走快速通道"]
    B --> C["main.tsx<br/>解析命令行参数"]
    C --> D["init.ts 全局初始化（只做一次）<br/>配置/安全环境变量/CA证书/预连API"]
    D --> E["setup.ts 会话初始化<br/>定工作目录/hooks 配置快照"]
    E --> F{"目录信任对话框<br/>❓信任此文件夹吗？"}
    F -->|信任| G["后台预热上下文<br/>预读 git 状态 + CLAUDE.md"]
    G --> H["渲染交互界面<br/>输入框出现！"]
    F -->|拒绝| I["退出"]
```

### 3.1 The sorter: why `--version` is lightning-fast

The program entry `main()` in `src/entrypoints/cli.tsx` is a parcel sorter. Look at this real source (around `cli.tsx:27-44`):

```ts
/**
 * Bootstrap entrypoint - checks for special flags before loading the full CLI.
 * All imports are dynamic to minimize module evaluation for fast paths.
 * Fast-path for --version has zero imports beyond this file.
 */
async function main(): Promise<void> {
  const args = process.argv.slice(2);

  // Fast-path for --version/-v: zero module loading needed
  if (args.length === 1 && (args[0] === '--version' || args[0] === '-v' || args[0] === '-V')) {
    // MACRO.VERSION is inlined at build time
    console.log(`${MACRO.VERSION} (Claude Code)`);
    return;   // ← 打印完直接 return，庞大的对话系统一行都没加载
  }
  // ……其他快速路径（--dump-system-prompt 等）……
  // 默认路径：await import('../main.js') 动态加载主程序
}
```

Line by line: the file-header comment states the design intent outright — "the `--version` fast path has **zero imports** beyond this file." The main program has a thousand modules; loading them all takes hundreds of milliseconds; `console.log` of a version string takes a few milliseconds. So the sorter looks at the arguments first: a simple request like `--version` prints and immediately `return`s; only the default path uses `await import('../main.js')` to **dynamically load** the main program. That is why `claude --version` comes back instantly, while `claude` needs a moment to start.

**Why this design?** A CLI tool's "felt speed" is largely decided by the response time of the simplest commands (imagine how annoyed you would be if `git --version` stalled for 2 seconds). Turning every heavy module into a dynamic import, trading "do less at startup" for "common paths are instant," is a classic CLI trade-off.

### 3.2 Global init: "opening the shop," done once

After entering the main program, `init()` in `src/entrypoints/init.ts` runs. Notice how it is defined (`init.ts:57`):

```ts
export const init = memoize(async (): Promise<void> => {
  // 读配置、设置安全相关环境变量、处理企业 CA 证书……
  // （init.ts:159）preconnectAnthropicApi()
})
```

`memoize` means: no matter how many places in the code call `init()`, **the function body actually runs once**; later calls return the first result. One especially interesting thing in init is `preconnectAnthropicApi()`: do a TLS handshake with Anthropic in advance. Like dialing the phone and leaving it off-hook, so when you actually speak you do not wait for the ring — that can save 100–200 milliseconds. **Why put it in init rather than at the first request?** Because the handshake can run in parallel with you "looking at the trust dialog, typing the first question" — free acceleration.

### 3.3 Session init: the hooks "anti-tamper" snapshot

`src/setup.ts` does session-level init: determine the current working directory (`setCwd`), handle `--worktree` and similar flags. One security detail: user-configured **hooks (hook scripts, chapter 7) are snapshotted here**. Why? Hooks are config that "automatically runs a shell command on your machine at certain moments," which is powerful; if someone quietly changed the config file mid-session (say a malicious script), the snapshot guarantees this session keeps using the config you confirmed at startup, against mid-session tampering.

### 3.4 The trust dialog: a real security boundary

The first time you run in a folder, it pops "do you trust this directory?" (`showSetupScreens` in `src/interactiveHelpers.tsx`). This is not a ceremonial welcome page; it is a security boundary: a project directory can hide other people's config (hooks, `CLAUDE.md`); **a malicious repo can use those to make your machine run dangerous commands**. Those project-level configs only take effect after you click "trust."

And the moment trust is granted, the software immediately **in the background** prefetches `getSystemContext()` (git status) and `getUserContext()` (`CLAUDE.md`). Both functions are memoize-cached — prefetch equals "heat the cache in advance," so your first question's response is faster. Same idea as the 3.2 API preconnect: **any preparation that can be done early is never left until the user speaks.**

> 🔍 **Source pointers**: `src/entrypoints/cli.tsx:27-44` (the sorter) ｜ `src/entrypoints/init.ts:57,159` (memoized init and preconnect) ｜ `src/setup.ts` (hooks snapshot) ｜ `src/interactiveHelpers.tsx` (trust dialog and warmup)

---

## 4. The moment you press Enter: how input is routed

The input box is up. You type a stretch of text and press Enter. From that second to "the request goes out" sits a chain much longer than you would guess.

### 4.1 From keypress to text: a journey from bytes to characters

Every key you type takes a journey:

```mermaid
flowchart LR
    A["键盘按键"] --> B["终端把按键变成<br/>原始字节流（stdin）"]
    B --> C["App.tsx parseMultipleKeypresses<br/>ANSI 转义序列状态机<br/><i>把字节解析成'结构化按键'</i>"]
    C --> D["useInput hook 分发"]
    D --> E["useTextInput.ts<br/>文本状态机：更新内容与光标"]
    E --> F{"是回车键？"}
    F -->|普通字符| E
    F -->|回车| G["onSubmit(完整文本)<br/><b>文本正式离开输入框</b>"]
```

Step 3 is worth explaining: in a terminal, arrow keys, Enter, and Ctrl combinations are not ordinary characters; they are "escape sequences" starting with ESC (pressing ↑ actually delivers 3 bytes `ESC [ A`). So the software needs a **state machine** to restore that byte stream into a structured event like "the user pressed up-arrow."

Enter handling is `handleEnter` in `src/hooks/useTextInput.ts:247`:

```ts
function handleEnter(key: Key) {
  if (multiline && cursor.offset > 0 &&
      cursor.text[cursor.offset - 1] === '\\') {
    // 行尾是反斜杠 \ → 用户想换行而不是提交
    return cursor.backspace().insert('\n')
  }
  if (key.meta || key.shift) {
    return cursor.insert('\n')   // Meta+Enter / Shift+Enter → 换行
  }
  // Apple Terminal 不支持自定义 Shift+Enter，用系统级修饰键检测兜底
  if (env.terminal === 'Apple_Terminal' && isModifierPressed('shift')) {
    return cursor.insert('\n')
  }
  onSubmit?.(originalValue)      // 以上都不是 → 才是真正的提交
}
```

Line by line: Enter **does not necessarily equal submit**. The first three branches all handle "the user actually wanted a newline" (trailing `\`, `Shift+Enter`, the Apple Terminal special case); only after all of those are excluded is `onSubmit` called. That is why you can write multiline text in the input box with `\` plus Enter.

### 4.2 A UI gate: another sieve before `onSubmit`

After text leaves `useTextInput`, it still has to pass the local `onSubmit` in `PromptInput.tsx` (around `:984`), which is responsible for several things "only the UI layer can judge":

- **Do not submit while a footer dialog is open** (you are looking at the help page; Enter is for the dialog);
- **Ghost-text accept**: that gray "guess what you wanted to type" line in the input box — pressing Enter may be accepting it, not submitting;
- **@teammate direct send**: in multi-agent collaboration, `@name content` is routed directly to that member;
- **Empty input is dropped**.

All of that passing, `onSubmit` in `src/screens/REPL.tsx:3142` takes over — and from here on it is the real "sorting desk."

### 4.3 The sorting desk: pure string matching, zero model involvement

Many people assume the sorting desk "intelligently understands" intent. In fact it uses a few **hard-coded string rules**, all the most naive comparison.

**Rule 1: look at the first character** (`src/components/PromptInput/inputModes.ts:16`):

```ts
export function getModeFromInput(input: string): HistoryMode {
  if (input.startsWith('!')) {   // 首字符是 ! → bash 模式
    return 'bash'
  }
  return 'prompt'                // 否则 → 候选发给 AI
}
```

Slash commands are the same idea: `inputString.startsWith('/')` in `processUserInput.ts`.

**Rule 2: blank input is dropped** (`src/utils/handlePromptSubmit.ts:188`): `if (input.trim() === '') return` — you typed a few spaces and hit Enter, nothing happens.

**Rule 3: exit words are a hard-coded list** (`handlePromptSubmit.ts:196`):

```ts
if (['exit', 'quit', ':q', ':q!', ':wq', ':wq!'].includes(input.trim())) {
  // 改写成 /exit 命令，走正常的退出流程（还会弹反馈对话框）
}
```

Note it is `.includes()` — the input must **exactly equal** one of the words. "help me exit this infinite loop" in natural language does not match and is sent to the model as usual. (`:q` and `:wq` are easter eggs for Vim muscle memory.)

**Rule 4: slash commands are a dictionary lookup, also exact match** (`src/commands.ts:688`):

```ts
export function findCommand(commandName: string, commands: Command[]) {
  return commands.find(
    _ =>
      _.name === commandName ||            // 名字精确相等
      getCommandName(_) === commandName || // 或全名精确相等
      _.aliases?.includes(commandName),    // 或命中别名列表
  )
}
```

A miss returns `Unknown command: xxx` honestly. It will never "guess" you meant a different command.

**Why design it this "dumb"?** A deliberate engineering trade-off:

1. **Determinism**: whether your input goes to the model involves privacy and cost, and must be 100% predictable. If you used "smart classification," a mis-fire could upload a private aside;
2. **Speed**: string comparison is nanoseconds, zero latency, zero tokens;
3. **Debuggability**: the rules are in the code; behavior is fully reproducible.

The place that actually "understands" your input is **after** sorting. The sorting desk is a bouncer who follows the book: it does not know people, it only checks IDs.

### 4.4 Seven inputs, seven fates

String the rules together and watch where seven real inputs go (this is the most important figure in the chapter):

```mermaid
flowchart TD
    INPUT["你敲下的文本"] --> Q1{"trim 后为空？"}
    Q1 -->|如只敲了空格| DROP["🗑️ 丢弃<br/><i>handlePromptSubmit.ts:188</i>"]
    Q1 -->|否| Q2{"精确等于 exit/quit/:q/:q!/:wq/:wq!？"}
    Q2 -->|是| EXIT["本地执行 /exit，退出程序<br/><b>不发 AI</b>"]
    Q2 -->|否| Q3{"! 开头？"}
    Q3 -->|是| BASH["💻 bash 模式：本地直接执行 shell<br/>输出只给你看<br/><b>不发 AI（shouldQuery:false）</b>"]
    Q3 -->|否| Q4{"/ 开头？"}
    Q4 -->|是| CMD{"findCommand 查命令表"}
    CMD -->|local-jsx 型<br/>如 /config| LOCALUI["⚙️ 打开本地界面<br/><b>不发 AI</b>"]
    CMD -->|local 型<br/>如 /clear| LOCAL["⚙️ 本地执行（清空对话）<br/><b>不发 AI</b>"]
    CMD -->|prompt 型<br/>如 /commit、技能| PROMPT["📝 展开成一段预制提示词<br/><b>发给 AI</b>"]
    CMD -->|查无此令| UNKNOWN["提示 Unknown command<br/><b>不发 AI</b>"]
    Q4 -->|否| Q5{"AI 正在忙吗？<br/><i>queryGuard.isActive</i>"}
    Q5 -->|在忙| QUEUE["📥 进 messageQueueManager 排队<br/>当前回合结束后再处理"]
    Q5 -->|空闲| TEXT["✉️ 包装成用户消息<br/><b>发给 AI（shouldQuery:true）</b>"]
```

Walk the examples one by one:

1. **`!ls -la`** → bash mode. The command runs on your machine, the output is shown to you, `shouldQuery: false` (`processUserInput.ts:206`) — **not one byte is sent to the model**. For when you want to run a command yourself, quickly.
2. **`/clear`** → a `local` slash command: locally clear conversation history, not sent to the model. That is why `/clear` does not consume API quota.
3. **`/config`** → `local-jsx`: render a settings UI in the terminal for you to click, also not sent to the model.
4. **`/commit`** → `prompt` type: the software expands the command into a prefabricated prompt (roughly "look at git status, write a commit message and commit"), and **that expanded text** is what goes to the model. Skills take this path too.
5. **`help me fix a bug`** → ordinary question: wrapped as a user message and sent to the model, `shouldQuery: true`.
6. **A message you sent while the model was busy** → into the `messageQueueManager` queue. When the current turn ends, queued messages are taken out and run through the sorting pipeline again.
7. **`exit` or `:q`** → local exit. Note it is rewritten as `/exit` and takes the normal command path, not a crude `process.exit` — so exit can still clean up and pop a feedback dialog.

Two more points that often confuse:

- **Does history recalled with ↑ get sent to the model?** No. That history lives locally in `~/.claude/history.jsonl` (`src/history.ts`) and only serves "up-arrow recall." It is a different object from the conversation history sent to the model.
- **Before send there is still a user-installed security checkpoint: the UserPromptSubmit hook** (`processUserInput.ts:174-264`). If you have a hook script in config, it can intercept your input here, rewrite it, or append extra material. Chapter 7 covers the hook mechanism.

### 4.5 Attachments: how `@file` is stuffed in

If the input contains `@src/main.ts`, or your IDE has a selection, those are collected at this stage by `getAttachmentMessages` in `src/utils/attachments.ts` as "attachment messages" and sent after your question. Pasted images are also compressed here and turned into image blocks the model can read. Skill-listing attachments (the "progressive disclosure" of chapter 5) take this channel too.

> 🔍 **Source pointers**: `src/hooks/useTextInput.ts:247` (Enter handling) ｜ `src/components/PromptInput/PromptInput.tsx:984` (UI gate) ｜ `src/screens/REPL.tsx:3142` (REPL `onSubmit`) ｜ `src/utils/handlePromptSubmit.ts:188,196` (blank drop, exit list) ｜ `src/components/PromptInput/inputModes.ts:16` (`!` test) ｜ `src/commands.ts:688` (`findCommand`) ｜ `src/utils/processUserInput/processUserInput.ts:174-264,446` (UserPromptSubmit hook, bash mode) ｜ `src/utils/attachments.ts` (attachment collection)

---

## 5. Packing the suitcase: how a request to the model is assembled

Your question has been confirmed as "send to the model." You thought the only thing going over was "help me see whether this function has a bug"? In fact every request is a large parcel:

```mermaid
flowchart TB
    subgraph PACKAGE["📦 发往 Anthropic API 的一个完整请求"]
        direction TB
        SYS["1️⃣ system 字段：系统提示词<br/>静态段（角色/规范/工具说明）+ 动态段（技能清单/环境信息）<br/><b>末尾追加</b> gitStatus"]
        MSG0["2️⃣ 对话第一条 user 消息（isMeta 隐身）<br/>&lt;system-reminder&gt; 包裹的 CLAUDE.md + 今天日期"]
        HISTORY["3️⃣ 对话历史<br/>你说过的话 + AI 每次回复 + 每次工具结果，原封不动全带"]
        NEW["4️⃣ 你的新消息 + @附件/图片"]
        TOOLS["5️⃣ tools 参数：约 40 个工具的说明书<br/>名字 + 用途描述 + 参数 JSON Schema"]
    end
```

This material is collected **on three parallel paths** (source: `getSystemPrompt` at `src/constants/prompts.ts:444`, `getUserContext`/`getSystemContext` in `src/context.ts`):

- `getSystemPrompt` → the system prompt (piece 1);
- `getUserContext` → `CLAUDE.md` + today's date (piece 2);
- `getSystemContext` → git status (appended at the end of piece 1).

The three pieces are fetched in parallel (all caches warmed at startup), then two functions in `src/utils/api.ts` "seat them" in the request. Piece by piece.

### 5.1 The system prompt: Anthropic's "employee handbook" for the model

The `system` field on every request is Anthropic's employee handbook for the model, in two segments:

- **Static segment**: role definition ("you are Claude Code, a programming assistant"), behavioral rules ("you must read a file before editing it," "keep answers concise"), tool-use rules, dozens of clauses. The static segment is basically the same for every user — **that is deliberate**. An identical prefix hits the API's prompt cache (the cached portion is not billed / billed less), so `getAllBaseTools()` (`src/tools.ts:193`) carries a comment warning that changes must stay in sync with the cache config.
- **Dynamic segment**: the currently available skill listing, a memory summary, environment facts (OS / shell / model version).

### 5.2 `CLAUDE.md` collection rules: five sources, layered

`CLAUDE.md` is not "read one file." It is a collection pipeline with a strict order (`getMemoryFiles` at `src/utils/claudemd.ts:790`), **collecting 5 sources in sequence** and concatenating all of them:

```mermaid
flowchart TD
    A["开始收集记忆文件"] --> B["1️⃣ Managed（企业管控）<br/>/etc/claude-code/CLAUDE.md + 管控版 rules<br/><i>最先加载：公司 IT 下发的强制规范</i>"]
    B --> C["2️⃣ User（用户全局）<br/>~/.claude/CLAUDE.md + ~/.claude/rules/*.md<br/><i>你个人对所有项目的偏好</i>"]
    C --> D["3️⃣ Project + Local（项目）<br/>从当前目录<b>逐级向上走到根目录</b><br/>每层找 4 个目标："]
    D --> E["· CLAUDE.md（项目规范，提交进 git）<br/>· .claude/CLAUDE.md（另一种放法）<br/>· .claude/rules/*.md（拆成多条的规则）<br/>· CLAUDE.local.md（私人项目笔记，不提交 git）"]
    E --> F["4️⃣ --add-dir 额外目录<br/><i>需环境变量开启，默认关闭</i>"]
    F --> G["5️⃣ 自动记忆 / 团队记忆<br/><i>实验特性，feature gate 控制</i>"]
    G --> H["全部拼成一大段文字"]
```

A few details worth knowing:

1. **Walking upward means "nearest overlay"**: if you start in `projectA/src/utils`, the root, projectA, src, and utils each contribute their `CLAUDE.md` — files closer to you are more specific; all of them apply; none overwrite each other; the model decides which clause is more relevant.
2. **Every file gets an "identity tag"** (`getClaudeMds`, `claudemd.ts:1153`): when concatenated, each file is preceded by its path and type, e.g. `(project instructions, checked into the codebase)`, `(user's private global instructions for all projects)` — so the model knows which clause is a company mandate and which is your private note, and treats them differently.
3. **`@include` syntax and safety gates** (`processMemoryFile`, `claudemd.ts:618`): a `CLAUDE.md` can write `@path/file.md` to include another file; the software expands recursively, with four gates — at most 5 levels (`MAX_INCLUDE_DEPTH`); only a text-extension allowlist (so you cannot stuff images or PDFs into memory); **project-level files by default cannot include files outside the project directory** (so a malicious repo cannot steal your private files with `@~/.ssh/id_rsa`); every path goes into a `processedPaths` set for dedup (including symlink canonicalization). Per-file cap 40000 characters (`MAX_MEMORY_CHARACTER_COUNT`).
4. **Can be turned off entirely**: env `CLAUDE_CODE_DISABLE_CLAUDE_MDS` or `--bare` mode skips auto-discovery (`src/context.ts:165`).

### 5.3 Placement rules: instructions into `system`, memory disguised as the first user message

Once the material is collected, where it sits in the request is deliberate (`src/utils/api.ts`). Two real snippets:

```ts
// api.ts:437 —— git 状态：追加在系统提示词末尾
export function appendSystemContext(systemPrompt, context) {
  return [
    ...systemPrompt,
    Object.entries(context)
      .map(([key, value]) => `${key}: ${value}`)   // 拼成 "gitStatus: <内容>"
      .join('\n'),
  ].filter(Boolean)
}

// api.ts:449 —— CLAUDE.md + 日期：伪装成对话最早的一条 user 消息
export function prependUserContext(messages, context) {
  // ……空上下文直接返回……
  return [
    createUserMessage({
      content: `<system-reminder>\nAs you answer the user's questions,
you can use the following context:\n...（记忆内容+日期）...
IMPORTANT: this context may or may not be relevant...`,
      // isMeta: true —— 模型看得见，界面上不显示
    }),
    ...messages,
  ]
}
```

Line by line: `appendSystemContext` formats git status as `key: value` and concatenates it at the end of the system-prompt array — git status is "environment state," so sitting with instructions is most reasonable. `prependUserContext` wraps `CLAUDE.md` as a `<system-reminder>` user message and **inserts it at the head of conversation history**.

Why not put it in the `system` field too? Three design tricks:

1. **`isMeta: true` stealth flag** — the model can see it; your terminal UI does not display it; the dialog is not polluted;
2. **`IMPORTANT: this context may or may not be relevant`** — tells the model clearly "this is background, not a question," so it does not treat `CLAUDE.md` as something to answer;
3. **Cache-friendly**: git status and `CLAUDE.md` are read once per session (`memoize`) and resent identically every turn, which is exactly what hits the prompt cache, saving money and time — which is also why we "warm" them at startup (chapter 3).

### 5.4 Git status: a repo snapshot "as of session start"

The git information `getSystemContext` collects includes: current branch, main-branch name, `user.name`, `git status --short` (**truncated past 2000 characters**, `MAX_STATUS_CHARS = 2000` at `context.ts:20`), the last 5 commits. The content explicitly notes this is "**a snapshot as of session start**" — because collection is memoized, it will not refresh mid-session; the software chooses to tell the model honestly "this may be stale," rather than spend hundreds of milliseconds re-running git every turn. **Between accuracy and speed, it chose speed + an honest label.**

> 🔍 **Source pointers**: `src/constants/prompts.ts:444` (`getSystemPrompt`) ｜ `src/context.ts:20,165` (git truncation, kill switch) ｜ `src/utils/claudemd.ts:618,790,1153` (`@include`, collection order, identity tags) ｜ `src/utils/api.ts:437,449` (`appendSystemContext` / `prependUserContext`)

---

## 6. The heart: the agent loop (the most important chapter)

This chapter is the soul of the software. Understand it and you understand how every AI agent works.

### 6.1 The skeleton of the loop: a `while (true)`

The source is `src/query.ts`. The entry `query()` (`:219`) is only a shell; the real heart is `queryLoop` (`:241`). The core structure is surprisingly simple — a `while (true)` infinite loop plus a `State` struct held across turns (`:265-284`):

```ts
async function* queryLoop(params, consumedCommandUuids) {
  // 不可变参数：整个循环期间绝不重新赋值
  const { systemPrompt, userContext, systemContext, canUseTool,
          fallbackModel, querySource, maxTurns } = params

  // 可变状态：跨轮携带。每轮开头解构，继续时整体替换
  let state: State = {
    messages: params.messages,          // ← 对话历史，越滚越长
    toolUseContext: params.toolUseContext,
    maxOutputTokensRecoveryCount: 0,    // 输出截断恢复计数
    hasAttemptedReactiveCompact: false, // 被动压缩只试一次的标记
    turnCount: 1,
    // ……
  }

  while (true) {   // ← query.ts:307，Agent 的全部生命都在这个循环里
    // ① 压缩防线（第十二章）
    // ② deps.callModel 流式请求模型（:659）
    // ③ 收集 tool_use 块（:829-834）
    // ④ 无 tool_use → return completed（:1357）
    // ⑤ 有 tool_use → 执行工具，更新 state.messages，continue
  }
}
```

The source comments also hide an evolution: a checkpoint is named `query_recursive_call` (`:1714`); a historical comment says **this loop used to be recursive, then was rewritten as while + State**. Why change? Recursion deepens the call stack one frame per turn; after dozens of tool calls the stack is deeper and deeper, debug traces are ugly, and you cannot do unified cleanup at each "continue point." After `while + State`, all cross-turn state lives in one struct; the 7 continue points uniformly write `state = { ... }`; behavior is obvious. A typical trajectory of "write it correctly first, then refactor it clear."

### 6.2 What the loop looks like: one overview figure

```mermaid
flowchart TD
    START(["带着打包好的请求进入循环"]) --> COMPACT["① 压缩防线<br/><i>第十二章细讲</i>"]
    COMPACT --> CALL["② deps.callModel 流式请求<br/><i>src/services/api/claude.ts</i>"]
    CALL --> COLLECT["③ 边收边收集 tool_use 块<br/><i>needsFollowUp 是唯一核心循环信号</i>"]
    COLLECT --> CHECK{"有没有<br/>工具调用？"}
    CHECK -->|"没有"| DONE["🎉 跑 stop hooks 后<br/>return completed（:1357）"]
    CHECK -->|"有"| EXEC["④ 执行工具<br/><i>第七章细讲</i>"]
    EXEC --> APPEND["⑤ state.messages =<br/>[...旧历史, ...assistant回复, ...toolResults]<br/><i>query.ts:1716</i>"]
    APPEND --> COMPACT
```

In one sentence:

> **As long as the model still wants a tool, the loop does not stop. The moment the model stops asking for tools, the answer is out.**

### 6.3 A full demo: "find every TODO in the project and summarize"

A real three-turn trace. You say: "find every TODO comment in the project and summarize."

| Turn | History sent to the model (accumulating) | The model's reply | What the software does |
|---|---|---|---|
| Turn 1 | employee handbook + git status + your question | `tool_use`: Grep(pattern="TODO") | run the search; dozens of matches appended to history |
| Turn 2 | all of turn 1 + Grep results | `tool_use`: Read(file 1) and Read(file 2) (**in parallel**) | two read-only tools run together; file contents appended to history |
| Turn 3 | all of the first two turns + file contents | plain text: "found 23 TODOs, concentrated in 3 files…" | **no `tool_use` → loop ends**, display the answer |

```mermaid
sequenceDiagram
    participant 循环 as queryLoop
    participant 模型
    participant 工具 as 工具执行器

    循环->>模型: 第1圈：问题+历史
    模型-->>循环: tool_use: Grep("TODO")
    循环->>工具: 执行 Grep
    工具-->>循环: "a.ts:12, b.ts:88, c.ts:5 ..."
    循环->>模型: 第2圈：历史+Grep结果
    模型-->>循环: tool_use: Read(a.ts) + Read(b.ts)
    par 只读工具并行（上限10个）
        循环->>工具: Read a.ts
        循环->>工具: Read b.ts
    end
    工具-->>循环: 两个文件内容
    循环->>模型: 第3圈：更长的历史
    模型-->>循环: 纯文字汇总（无 tool_use）
    循环->>循环: return completed → 显示答案
```

Notice history **grows every turn**: turn 3's request contains the entire conversation of the first two turns. That is why long conversations get slower and more expensive, and why chapter 12's "compaction" exists.

### 6.4 Streaming receive: why the answer is "typed" one character at a time

Generating a reply takes time; Claude Code uses **streaming** transport: the server writes a bit and sends a bit. The request-issuing code (near `claude.ts:1822`) has a telling comment:

```ts
// Use raw stream instead of BetaMessageStream to avoid O(n²) partial JSON parsing
// BetaMessageStream calls partialParse() on every input_json_delta, which we don't need
// since we handle tool input accumulation ourselves
const result = await anthropic.beta.messages.create(
  { ...params, stream: true },
  { signal, ... },   // ← signal：ESC 急刹车的"刹车线"直接通到这里
)
```

Line by line: the software **deliberately does not use the SDK's high-level stream object**, and uses the raw stream. Why? A tool call's arguments are JSON, and in streaming they arrive as tiny fragments (`input_json_delta` events); the SDK tries to parse "JSON that is not yet complete" on every fragment; the longer the content, the more repeated parsing, an O(n²) waste. Claude Code concatenates the fragments **into a string and parses once when complete**, skipping all the repeated work. When you once ask the model to write a large file, the gap from this optimization is very visible.

The streaming format is called SSE (Server-Sent Events). After the software receives it, it enters a large `switch` (`claude.ts:1980-2303`) to sort:

```mermaid
flowchart LR
    SSE["服务器 SSE 事件流"] --> SW{"事件类型 switch"}
    SW -->|"content_block_start<br/>(:1995)"| A["初始化一个内容块<br/>（文字块或 tool_use 块）"]
    SW -->|"input_json_delta<br/>(:2087)"| B["把工具入参的 JSON 碎片<br/>逐段拼接到字符串上"]
    SW -->|"content_block_stop<br/>(:2171)"| C["块收齐了 → 组装 yield<br/>JSON 只在此刻解析一次"]
    SW -->|"message_delta<br/>(:2213)"| D["回填 usage 统计和<br/>stop_reason（模型为何停笔）"]
```

Matching life-support: a stream **idle watchdog of 90 seconds** (if the server is silent for that long, treat it as a broken stream, error and retry); **model fallback** — when the current model is overloaded, automatically switch to a backup and retry.

### 6.5 A clever optimization: start work while still receiving

The model's words are not finished, and the tool is already running. That is `StreamingToolExecutor` (`src/services/tools/StreamingToolExecutor.ts`): **the moment a tool call's arguments are complete, start executing without waiting for the rest of the model's explanation.** For example, the first half of the reply is "I want to run `npm test`"; while the explanatory second half is still in flight, `npm test` is already running on your machine.

Execution discipline is decided by the tool's `isConcurrencySafe` property (`StreamingToolExecutor.ts:105-133`):

- **read-only tools may run in parallel** (reading files and searching do not interfere), concurrency cap 10 (env `CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY` is tunable);
- **writes are strictly queued**: if even one unsafe tool is in the same batch, everyone goes one at a time, so they do not step on each other;
- **a Bash error cancels sibling commands** (`siblingAbortController`, `:48-59`): if `mkdir foo` in a batch of commands fails, the later "write a file into foo" is cancelled immediately — because commands often have a dependency chain, and if the front collapsed the back is pointless.

### 6.6 A key invariant: every "letter" must have a "reply"

This is the easiest-to-miss, and hardest, rule in the whole system: **the API requires that every `tool_use` block be paired with a `tool_result`, otherwise the next request is refused outright (HTTP 400).**

But in reality the model may have issued a tool-call request and then been interrupted by ESC, by a model switch, by an exception — what about those "orphan `tool_use`"s? The answer is `yieldMissingToolResultBlocks` (`query.ts:123-149`): on interrupt / fallback / exception, the software **automatically patches a fake `tool_result` for every orphan** (content is an error gloss "interrupted," tagged `is_error: true`). Like a letter that must have a reply, even if the reply is "the other party does not want to talk." Without this patch, any interrupt would permanently wreck the conversation.

### 6.7 The loop's emergency exits: the full set of terminal reasons

Besides the normal exit `completed` (the model no longer calls tools), the loop has a whole set of emergency exits (collectively `Terminal` states in the source):

| Terminal reason | When it happens |
|---|---|
| ✅ `completed` | the model finished normally, no tool call |
| 🛑 `aborted_streaming` / `aborted_tools` | you pressed ESC during stream receive / tool execution |
| 📏 `prompt_too_long` | every compaction means failed; the window truly cannot hold it |
| 🔁 `max_turns` | hit the caller-set turn cap (e.g. the SDK's `maxTurns`) |
| 💥 `model_error` / `image_error` | the API keeps erroring / image processing failed |
| 🪝 `stop_hook_prevented` / `hook_stopped` | your configured Stop hook said "stop" |
| ⛔ `blocking_limit` | compaction is disabled and we hit the window warning line, leaving room for you to `/compact` by hand |

Two "life-extension" mechanisms are also worth a mention: **output-truncation recovery** — when the model's reply is truncated by `max_output_tokens`, the software has it "keep writing," recovering at most 3 times (`MAX_OUTPUT_TOKENS_RECOVERY_LIMIT`); **token-budget nudge** — when money (the token budget) is almost gone, inject a meta message into the conversation urging the model to "wrap up."

> 🔍 **Source pointers**: `src/query.ts:219,241,265-284,307,659,829-834,1357,1714-1716` (loop skeleton) ｜ `src/query.ts:123-149` (patch orphan `tool_result`) ｜ `src/services/api/claude.ts:1822,1980-2303` (raw stream and SSE switch) ｜ `src/services/tools/StreamingToolExecutor.ts:48-59,105-133` (concurrency discipline and sibling cancel)

---

## 7. The model's "hands": the tool system and the permission gate

### 7.1 What a tool is: an object of about 40 fields

Every capability the model can use is a "Tool" object (defined in `src/Tool.ts`), about 40 built-in tools. Each tool is made of five groups of fields:

| Group | Fields | What they do |
|---|---|---|
| Identity | `name` / `aliases` | the model calls by name |
| Schema | `inputSchema` (Zod) | parameter format; `zodToJsonSchema` turns it into JSON Schema for the API |
| Behavior | `call` / `validateInput` / `isReadOnly` / `isConcurrencySafe` | the function that actually works, plus concurrency properties |
| Permission | `checkPermissions` | the tool itself judges "is this particular operation dangerous" |
| UI | `renderToolUseMessage` etc. | how it is shown in the terminal (Bash shows `$ command`, Edit shows a diff) |

Every tool is minted uniformly by the `buildTool` factory (`Tool.ts:783`). The factory fills in **fail-closed defaults** — any field you did not define explicitly defaults to the most conservative behavior (prefer one extra ask over one wrong allow). The **single source of truth** for all tools is `getAllBaseTools()` (`tools.ts:193`); about 40 built-ins are registered there; then `getTools()` filters by current mode and strips by deny rules; `assembleToolPool()` merges MCP tools, dedupes, and sorts — **stable sort matters**, because the tool list is sent to the API every turn, and a stable order is what hits the prompt cache.

How does a tool's "spec sheet" get into the model's hands? `toolToAPISchema` (`utils/api.ts:119`) turns each tool into `{name, description: await tool.prompt(), input_schema}`, sent as the `tools` parameter of every turn (`claude.ts:1235`). When there are too many tools there is also a **ToolSearch** mechanism (`defer_loading`): first give the model only a "tool-search tool"; it looks up the tools it needs by keyword and then loads the full schema — a bookshelf that first only shows titles, and you pull the book you need.

### 7.2 Permission adjudication: a five-gate lock

This is the core of the system's safety. The model has **no way** to execute a command directly — it can only "file a request"; approval sits with the software (and you). The adjudication function is `hasPermissionsToUseToolInner` (`src/utils/permissions/permissions.ts:1158`); the gate order is as follows (source comments labeled as written):

```ts
async function hasPermissionsToUseToolInner(tool, input, context) {
  // 1. Check if the tool is denied        ← 第一关：deny 规则（一票否决）
  const denyRule = getDenyRuleForTool(appState.toolPermissionContext, tool)
  if (denyRule) return { behavior: 'deny', ... }

  // 1b. Check if the entire tool should always ask  ← ask 规则（逢用必问）
  const askRule = getAskRuleForTool(...)
  // ……然后是：工具自己的 checkPermissions（Bash 在此解析命令）
  // ……再是：权限模式（default/plan/acceptEdits/bypassPermissions/dontAsk/auto）
  // ……再是：alwaysAllow 规则（你以前点过"始终允许"的）
  // ……五关都不中 → 兜底 ask：弹窗问你
}
```

```mermaid
flowchart TD
    A["模型申请：Bash('某命令')"] --> B{"① deny 规则？"<br><i>如你把 rm 加进黑名单</i>}
    B -->|命中| DENY["❌ 拒绝，原因回喂模型"]
    B -->|否| C{"② ask 规则？"<br><i>逢用必问的清单</i>}
    C -->|命中| ASK["❓ 弹窗问你"]
    C -->|否| D{"③ 工具自查 checkPermissions<br><i>Bash：解析命令 AST，匹配前缀规则<br>如 Bash(git *)，比对危险模式清单</i>"}
    D -->|判定危险| DENY
    D -->|判定安全| ALLOW["✅ 直接执行"]
    D -->|拿不准| E{"④ 权限模式<br><i>plan 模式全拦截 / acceptEdits 放行改文件<br>bypassPermissions 全放行 / auto 交给分类器</i>"}
    E -->|模式说放行| ALLOW
    E -->|模式说拦| F{"⑤ alwaysAllow 规则？<br><i>你以前点过'始终允许 npm test'？</i>"}
    F -->|命中| ALLOW
    F -->|否| ASK
    ASK -->|允许一次| ALLOW
    ASK -->|始终允许| WRITE["✅ 执行 + 规则写入 settings<br>下次不再问"]
    ASK -->|拒绝| DENY
```

Bash's "self-check" is worth unpacking: it parses the command into an AST (syntax tree) and does two matches — **prefix rules** (you configured `Bash(git *)` as allow, so `git status` and `git log` are let through); **dangerous-pattern list** (`src/utils/permissions/dangerousPatterns.ts` maintains a list of "interpreters that can run arbitrary code" such as `python`, `node`, `npx`, `bash`; allow rules with those prefixes get extra scrutiny — because `Bash(python *)` equals opening everything). In some scenes the software also lets a small model "pre-score" the command's danger (`startSpeculativeClassifierCheck` speculative classifier); high-confidence-safe commands are let through, reducing dialog interruption.

### 7.3 Four commands, four fates

Drop the adjudication onto four concrete examples:

| Command | Fate | Gates it walked |
|---|---|---|
| `git status` | ✅ **let through** | you previously clicked "always allow `git *`" → hit gate ⑤ alwaysAllow; or the tool's self-check judged it read-only-safe |
| `rm -rf /` | ❌ **blocked outright** | hit a dangerous pattern / deny rule; gate ① or ③ vetoes, no dialog at all — **some commands should not even be asked** |
| `npm test` | ❓ **dialog asks you** | none of the five gates hit → fallback ask. "Allow once" runs only this time; "always allow" writes the rule into config, and later `npm test` stops bothering you |
| After the model sees "denied" | 🔁 **self-corrects** | the denial reason is fed back as a tool result; the model usually tries again in a different posture (e.g. finish the task with a read-only method) |

The fourth case reveals a design philosophy: **a model mistake does not crash the system**. The model invents a tool name, the software replies `No such tool` and lets it self-correct; arguments are wrong, Zod validation bounces them; a command is denied, the reason is fed back. Every failure is translated into "textual feedback" the model can read; the loop continues — the program does not crash.

### 7.4 The execution chain in full: from "the model wants to" to "it actually runs"

```mermaid
flowchart TD
    A["模型说：tool_use Bash('rm -rf node_modules')"] --> B["1. findToolByName 找工具<br><i>找不到 → 回 'No such tool' 让模型自我纠正</i>"]
    B --> C["2. inputSchema.safeParse 校验参数<br><i>command 是字符串吗？</i>"]
    C --> D["3. validateInput 工具自检"]
    D --> E["4. PreToolUse 钩子<br><i>toolHooks.ts:435，用户脚本可拦截/改写</i>"]
    E --> F{"5. canUseTool 权限裁决<br><i>7.2 节的五道关卡</i>"}
    F -->|拒绝| G["拒绝原因回喂模型"]
    F -->|放行| H["6. tool.call 真正执行（:1207）<br><i>起 shell 进程，收集输出</i>"]
    H --> I["7. 结果格式化 mapToolResultToToolResultBlockParam"]
    I --> J["8. PostToolUse 钩子<br><i>可对结果追加反馈</i>"]
    J --> K["9. 包成 tool_result 的 user 消息<br>追加进对话历史 → 主循环下一圈"]
```

(Source spine: `runToolUse` at `src/services/tools/toolExecution.ts:337`.) A tool execution throwing an exception also does not crash: the exception is wrapped as a `tool_result` with `is_error: true` and fed back; the model reads the error and thinks of another way.

### 7.5 Hooks: a user-installed "automated checkpoint"

A hook is a shell command you register in a config file, triggered at 20 kinds of event (PreToolUse / PostToolUse / UserPromptSubmit / SessionStart / Stop / PreCompact, etc.). The execution mechanism (`src/utils/hooks.ts`) is `child_process.spawn` starting a subprocess to run your script: **JSON data in via stdin, your script writes JSON back on stdout**. The returned JSON can express `allow`/`deny`/`ask`/`hookUpdatedInput` (rewrite tool input) / `additionalContext` (append material for the model) / `stop` (stop the loop).

Example: you can configure a PreToolUse hook that, whenever the model wants to Edit a file, first runs a format check; or a UserPromptSubmit hook that auto-translates every question of yours into English before sending it to the model. **The difference between hooks and the permission system**: permission is a gate built into the software; a hook is a pipeline you install yourself.

### 7.6 Skills and MCP: two ways to enlarge the toolbox

- **Skills**: `src/skills/` scans `~/.claude/skills`, the project's `.claude/skills`, plugin directories, and built-in directories; each `SKILL.md` (with YAML frontmatter) is a skill. It uses **progressive disclosure**: day to day, only a skill **listing** is injected into context (`getSkillListingAttachments` in `attachments.ts`, saving tokens); when the model decides to use a skill, it loads the full spec via the Skill tool.
- **MCP tools**: tools provided by an external MCP server are wrapped by `MCPTool` into a Tool object of the same shape, treated identically to built-ins — the same schema validation, the same permission adjudication, the same execution chain. To the model and the permission system, there is no way to tell whether a tool is built-in or foreign, which is the point of the design.

> 🔍 **Source pointers**: `src/Tool.ts:783` (`buildTool`) ｜ `src/tools.ts:193` (`getAllBaseTools`) ｜ `src/utils/api.ts:119` (`toolToAPISchema`) ｜ `src/utils/permissions/permissions.ts:1158` (adjudication order) ｜ `src/utils/permissions/dangerousPatterns.ts` (dangerous patterns) ｜ `src/services/tools/toolExecution.ts:337,1207` (execution chain) ｜ `src/services/tools/toolHooks.ts:435` (PreToolUse) ｜ `src/utils/hooks.ts` (spawn to run hooks)

This chapter is a "quick look" at the tool system: what a tool is, how permission governs. As for the look, parameters, and quirks of about 40 built-in tools — **the complete field guide, one by one, is chapter 8**; the full ecosystem of skills and MCP is chapter 10.

---

## 8. A field guide to the tools: the model's 40 weapons, one by one

Chapter 7 covered a tool object's field structure and the permission gate — that was the "institutional" quick look. This chapter dumps the whole toolbox on the table and looks at each piece: what each tool is named, what job it does, what parameters it eats, what quirks it has. Under `src/tools/` there are 39 tool directories (plus testing/ and shared/), plus the MCPTool family that wraps MCP tools, totaling 40-odd "weapons," **not one skipped**.

First three pieces of infrastructure every tool shares, then "field-guide cards" in 11 groups.

### 8.1 How the tool pool is assembled: register → filter → sort

The tool list in the model's hands is not a hard-coded table; it is assembled on the spot before every request, in three stages:

```mermaid
flowchart LR
    A["① getAllBaseTools() 注册<br/><i>tools.ts:193-251</i><br/><b>唯一事实来源</b>：约 40 个内置工具在此登记<br/>核心约 14 个无条件启用<br/>其余靠 feature()/环境变量门控"]
    A --> B["② getTools() 二次过滤<br/><i>tools.ts:271-327</i><br/>极简模式只留 Bash/Read/Edit<br/>deny 规则整体剔除（模型根本看不到）<br/>REPL 模式隐藏原语工具"]
    B --> C["③ assembleToolPool() 合并排序<br/><i>tools.ts:345-367</i><br/>内置与 MCP 工具合并去重（内置优先）<br/>各自按名排序，<b>内置必须排成连续前缀</b>"]
    C --> D["📮 作为 tools 参数发给 API"]
```

Each of the three stages has one point worth remembering:

1. **Single source of truth**: to know "which tools this version actually has," look only at the one function `getAllBaseTools()` — it carries a header comment warning that changes must stay in sync with the prompt-cache config (chapter 5: the tool list is part of the static prefix; messing with it scrambles the cache).
2. **Deny rules strip before the model sees the list** (`filterToolsByDenyRules` at `tools.ts:262-269`): if you blacklist a tool, it is not "requested then denied"; it **does not appear in the spec sheet at all** — the model does not even know it exists, cut off at the source.
3. **Built-ins must form a contiguous prefix**: MCP tools are appended after built-ins, not mixed by name. Why? Cache matches by prefix; if a foreign MCP tool is inserted into the middle of built-ins, every downstream cache key is scrambled, which is throwing money away every turn.

### 8.2 The three-flag system: every tool's "safety tags"

Every tool ships with three boolean flags (defaults at `Tool.ts:750-761` — **all default false**; undeclared means most conservative):

| Flag | Question it answers | Who reads it |
|---|---|---|
| `isReadOnly` | does this operation only look, not change? | the permission system (read-only is easier to allow), UI display |
| `isConcurrencySafe` | can it run at the same time as other tools? | the streaming executor (section 6.5: read-only parallel, writes serial, cap 10) |
| `isDestructive` | is this operation destructive? | permission warnings (the dialog adds a warning) |

The clever part is that these three flags **can be functions**, judged dynamically from input. Two typical cases:

- **Bash's `isReadOnly(input)`** (`BashTool.tsx:434-441`): not a one-cut "Bash is always dangerous," but parse the command itself — `ls`, `git status` count as read-only and parallelizable, `rm` does not;
- **ExitWorktree's `isDestructive(input)`** (`:168-170`): `action: "keep"` keeping the workspace is not destructive, `action: "remove"` deleting it is — the same knife, chopping vegetables versus chopping a person, treated differently.

### 8.3 When output is too large: the persist-to-disk mechanism

A tool result has to go back into conversation history, but one command's output may be hundreds of KB. Each tool has a `maxResultSizeChars` cap: Bash 30000 characters, Grep 20000 (one of the tightest caps in the whole toolbox, forcing the model to write a precise search), Read is Infinity (truncation is done in-house), most tools 100000. What happens after the cap?

```mermaid
flowchart TD
    A["工具执行完毕，结果超大"] --> B{"超过 maxResultSizeChars？"}
    B -->|没超| C["直接作为 tool_result 回传"]
    B -->|超了| D["完整输出写入临时文件落盘"]
    D --> E["tool_result 只带：开头摘要 + 文件路径<br/><i>persistedOutputPath，如 BashTool.tsx:424,292</i>"]
    E --> F["模型需要细节时<br/>用 Read/Grep 去落盘文件里自取"]
```

The idea is "do not show the model the whole picture, but leave it an index": history does not stuff 5MB of logs, only a sentence "the full output is at this path; read whichever slice you need." This and chapter 12's compaction-defense `applyToolResultBudget` are two applications of the same philosophy.

### 8.4 How to read a field-guide card

Every card below has a uniform format:

> **Name** — one-sentence job
> **Key parameters** · **call example** (JSON) · **details** (the 1–2 most characteristic implementation / safety designs, with line numbers)

Legend: 👀 read-only ｜ ⚡ concurrency-safe ｜ 🐢 delayed-loaded by ToolSearch (section 8.15).

---

### 8.5 File operations (4): the iron law of read-before-write

**`Read`** — the model's eyes: read a local file. 👀⚡
- Parameters: `file_path` (absolute path, required), `offset` (start line), `limit` (line count).
- Example: before fixing a bug `{"file_path": "/repo/src/index.ts", "offset": 100, "limit": 80}`.
- Details: **dual output caps** — a file over 256KB is refused outright (`MAX_OUTPUT_SIZE` at `utils/file.ts:48`); content over 25000 tokens errors (`limits.ts:18`, overridable by `CLAUDE_CODE_FILE_READ_MAX_OUTPUT_TOKENS`). Comments at `limits.ts:9-13` record an A/B rollback: after overshoot, "silent truncate" lost to "error" — because silent truncate lets the model think it finished the whole file and draw conclusions from incomplete information.

**`Edit`** — the workhorse for changing code: exact string replace.
- Parameters (`types.ts:6-19`): `file_path`, `old_string`, `new_string`, `replace_all` (default false).
- Example: `{"file_path": "/repo/app.ts", "old_string": "const x = 1", "new_string": "const x = 2"}`.
- Details: **a file that has not been Read errors outright** "File has not been read yet" (`FileEditTool.ts:281`); detecting an external change (you edited by hand, a linter edited) requires a fresh Read; `.ipynb` is refused and steered to NotebookEdit (`:270`) — each tool keeps its own jurisdiction.

**`Write`** — write / overwrite a whole file.
- Parameters: `file_path`, `content`.
- Example: `{"file_path": "/repo/new.ts", "content": "export const …"}`.
- Details: overwriting an existing file likewise forces "Read first," and detects "the file was changed since I last read it" (`FileWriteTool.ts:203,216`), so a blind overwrite does not wipe the content you just edited by hand.

**`NotebookEdit`** — edit a Jupyter notebook cell. 🐢
- Parameters: `notebook_path`, `cell_id`, `new_source`, `cell_type` (code/markdown), `edit_mode` (replace/insert/delete).
- Example: `{"notebook_path": "/repo/a.ipynb", "cell_id": "c3", "new_source": "print(1)", "edit_mode": "replace"}`.
- Details: `shouldDefer: true` (`:94`) — day to day the spec sheet is not even sent to the model; loaded when used (section 8.15).

Why is "read before write" an iron law? The model's picture of a file may be stale — you just changed two lines in the editor, it does not know. Forcing Read equals "refresh cognition" before write:

```mermaid
flowchart TD
    A["模型想 Edit/Write 一个文件"] --> B{"本会话 Read 过这个文件吗？"}
    B -->|没读过| C["❌ 报错 File has not been read yet<br/><i>FileEditTool.ts:281</i><br/>模型自我纠正：先 Read"]
    B -->|读过| D{"上次 Read 之后被外部改过吗？<br/><i>你手动改的 / linter 改的</i>"}
    D -->|改过| E["❌ 要求重新 Read<br/><i>FileWriteTool.ts:203,216</i>"]
    D -->|没改| F["✅ 执行写入"]
```

### 8.6 Search (2)

**`Glob`** — find files by filename pattern, sorted by mtime. 👀⚡
- Parameters: `pattern` (e.g. `**/*.ts`), `path` (search root, default current directory).
- Example: find all test files → `{"pattern": "**/*.test.ts"}`.
- Details: the ant internal build, because it inlines substitutes like bfs/ugrep, **removes Glob/Grep from the tool list entirely** (`tools.ts:198-201`) — the toolbox also "diets" by edition.

**`Grep`** — content regex search via ripgrep. 👀⚡
- Parameters: `pattern`, `path`, `glob`, `output_mode` (content/files_with_matches/count), `-A/-B/-C` (context lines), `-n`, `-i`, `type`, `head_limit` (default 250), `offset`, `multiline`.
- Example: `{"pattern": "fetchUser", "output_mode": "content", "-n": true}`.
- Details: `maxResultSizeChars: 20_000` (`GrepTool.ts:164`) — as said above, one of the tightest caps in the whole toolbox. Design intent: overly broad search results are a common disease of models; a hard cap forces the regex to be written accurately.

### 8.7 Command execution (3)

**`Bash`** — the model's two hands: run a shell command.
- Parameters (`:227-247`): `command`, `timeout`, `description` (**optional**, `z.string().optional()` — a plain-language description for humans), `run_in_background`, `dangerouslyDisableSandbox`.
- Example: `{"command": "npm test", "description": "Run test suite"}`.
- Detail 1: **compound commands are split then matched**. When the model requests `ls && git push`, `preparePermissionMatcher` splits it into subcommands and runs each through permission rules (`BashTool.tsx:447-465`) — you having allowed `Bash(ls *)` does not mean `git push` can hitch a ride; see the figure below.
- Detail 2: **internal fields hidden from the model**. `_simulatedSedEdit` is an internal marker "after a sed preview is approved," deliberately omitted from the model-visible schema (`:249-259` comments) — so the model cannot use it to bypass the permission sandbox and write arbitrary files. Output over 30000 characters takes the persist-to-disk of section 8.3 (`:424,292`).

```mermaid
flowchart TD
    A["模型申请：Bash('ls && git push')"] --> B["preparePermissionMatcher<br/>把复合命令拆成子命令<br/><i>BashTool.tsx:447-465</i>"]
    B --> C["子命令 1：ls"]
    B --> D["子命令 2：git push"]
    C --> E{"匹配 Bash(ls *)？<br/>✅ 已允许"}
    D --> F{"匹配 Bash(git push *)？<br/>❓ 没有规则"}
    E --> G["任何一条过不了，整条命令都要弹窗<br/><b>权限按最严的子命令算</b>"]
    F --> G
```

**`PowerShell`** — the PowerShell edition of Bash on Windows.
- Parameters almost identical to Bash (`:228-233`).
- Enablement: `getPlatform()==='windows'`, and ant on by default / external needs `CLAUDE_CODE_USE_POWERSHELL_TOOL` (`utils/shell/shellToolUtils.ts:17-22`; `tools.ts:242`). The directory ships its own safety checks such as `gitSafety.ts`, `destructiveCommandWarning.ts`.

**`REPL`** — an ant-internal "batch-operate a VM" mode switch.
- Enablement: `USER_TYPE==='ant'` and the CLI entry; `CLAUDE_CODE_REPL=0` can turn it off (`REPLTool/constants.ts:23-30`); SDK does not enable it by default.
- Details: when on, the 8 primitive tools Read/Write/Edit/Glob/Grep/Bash/NotebookEdit/Agent are **hidden** from the model (`REPL_ONLY_TOOLS :37-46`); the model must call them through REPL inside the VM — equivalent to swapping "act directly" for "act in a sandbox."

### 8.8 Network (2)

**`WebFetch`** — fetch a URL's content, then run a small prompt so the model processes that content and returns it. 👀🐢
- Parameters: `url`, `prompt` (what to do with the fetched content).
- Example: `{"url": "https://docs.example.com/install", "prompt": "提取安装步骤"}`.
- Details: `preapproved.ts:14` maintains a "code-related domain allowlist," only allowing WebFetch GETs to skip review; the file header has a SECURITY WARNING (`:5-11`): **this allowlist must not be reused by other tools** — the allowlist was set against a "read-only webpage" threat model; another tool borrowing it would be out of scope.

**`WebSearch`** — web search. 🐢
- Parameters: `query` (≥2 characters), `allowed_domains`, `blocked_domains`.
- Example: `{"query": "vite 7 migration guide"}`.
- Details: `isEnabled()` is decided by API provider — firstParty is on directly; Vertex requires a Claude 4.0+ model. Feature availability depends on "how you pay" (chapter 11 covers the auth system).

### 8.9 Sub-agents and task management (8)

**`Agent`** — spawn a sub-agent to finish a task independently (isolated context; can run in parallel or in the background).
- Parameters (`:82-126`): `description` (3–5 words), `prompt` (the brief), `subagent_type`, `model` (sonnet/opus/haiku), `run_in_background`, `name`/`team_name`, `mode`, `isolation: worktree`, `cwd`.
- Example: three `{"description": "调研方案A", "prompt": "…", "run_in_background": true}` exploring three routes in parallel.
- Details: `isReadOnly()` returns true — the Agent tool **itself does not write disk**; permission checks are all delegated to tools inside the sub-agent (`:1264-1266`); output cap 100000 characters (`:229`). Chapter 13 covers the two run modes of a sub-agent.

**`TaskOutput`** — read a background task's output; can block and wait. 👀
- Parameters: `task_id`, `block` (default true), `timeout` (≤600000ms, default 30000).
- Example: `{"task_id": "a1b2c3", "block": true, "timeout": 30000}`.
- Details: `isEnabled()` is hard-coded `"external" !== 'ant'` (near `:163`) — the ant internal build actually disables this tool, another example of edition differences.

**`TaskStop`** — terminate a background task. 🐢
- Parameters: `task_id` (compatible with the old name `shell_id`).
- Example: `{"task_id": "b7"}`.

**`TaskCreate` / `TaskGet` / `TaskUpdate` / `TaskList`** (the TodoV2 quartet) — structured task-list create/get/update/list. 🐢
- Traits: support blocking relations `addBlocks`/`addBlockedBy`, `owner`, `metadata`; TaskUpdate's status accepts `'deleted'`.
- Enablement: `isTodoV2Enabled()` (`tools.ts:218-220`) — forced on in non-interactive sessions; `CLAUDE_CODE_ENABLE_TASKS` forces it on (`utils/tasks.ts:133-139`).

**`TodoWrite`** — classic todo-list write (`content`/`status`/`activeForm`).
- Details: **mutually exclusive with the TaskCreate family** — `isEnabled() { return !isTodoV2Enabled() }` (near `:51`). The old and new todo systems are one-or-the-other; they never appear in the toolbox together:

```mermaid
flowchart LR
    Q{"isTodoV2Enabled()？<br/><i>utils/tasks.ts:133-139</i>"}
    Q -->|"是（新）"| A["✅ TaskCreate/Get/Update/List 上岗<br/>❌ TodoWrite 隐身"]
    Q -->|"否（旧）"| B["✅ TodoWrite 上岗<br/>❌ Task 四件套不注册"]
```

### 8.10 Multi-agent collaboration (3)

**`SendMessage`** — send a message to a teammate agent.
- Parameters: `to` (teammate name, `*` broadcast; under `UDS_INBOX` also `uds:<socket>`, `bridge:<session>` cross-process delivery), `summary` (5–10 words UI preview), `message`.
- Example: `{"to": "researcher", "summary": "询问认证方案结论", "message": "你查的 OAuth 方案有结论了吗？"}`.
- Details: `isReadOnly(input)` is dynamic — a pure-text message counts as read-only; `isEnabled()` = `isAgentSwarmsEnabled()`.

**`TeamCreate` / `TeamDelete`** — create / dissolve an agent team.
- TeamCreate parameters: `team_name`, `description`, `agent_type`.
- Enablement: `isAgentSwarmsEnabled()` — always on for ant; external needs `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS` or `--agent-teams` (`utils/agentSwarmsEnabled.ts:24-32`; `tools.ts:228-230` uses lazy require to break a cyclic dependency).

### 8.11 User interaction and configuration (4)

**`AskUserQuestion`** — pop 1–4 multiple-choice questions at you (with option annotations) and take a structured answer. 👀⚡
- Parameters: `questions` (array of 1–4, with uniqueness-check refine).
- Example: `{"questions": [{"question": "选哪种数据库？", "options": [{"label": "Postgres"}, {"label": "SQLite"}]}]}`.
- Details: when `--channels` (Telegram/Discord remote channels) is active, `isEnabled()` returns false (`:135-145`) — nobody is sitting at the terminal, so a dialog would **hang the whole session**. Feature availability depends on "is there a person in front of you."

**`Brief` (API name `SendUserMessage`)** — in KAIROS proactive-assistant mode, send you a rich-text message, optionally with attachments; `status: proactive` for a proactive push.
- Details: the directory is named Brief but the API name is SendUserMessage (`prompt.ts:1`); `isEnabled()` requires both `feature('KAIROS')` and a runtime entitlement (`:122-135`).

**`Skill`** — invoke an installed skill by name (inject its `SKILL.md` instructions).
- Parameters: `skill`, `args`.
- Example: `{"skill": "commit"}`.
- Details: this is the stage-two switch of "progressive disclosure"; chapter 10 covers it.

**`Config`** — read/change a setting.
- Parameters: `setting`, `value` (omit = read).
- Details: `isReadOnly(input)` is dynamic — a read with no `value` counts as read-only (`:90-92`); registered only when `USER_TYPE==='ant'` (`tools.ts:214`).

### 8.12 Mode switching (4)

**`EnterPlanMode`** — enter "plan only, do not act." No parameters (`EnterPlanModeTool.ts:21-26`).

**`ExitPlanMode`** — submit a plan for your approval.
- Parameters: `allowedPrompts` — declare the permission categories needed to implement the plan (describes **action categories**, not concrete commands, e.g. "run tests").
- Details: when channels are active, the enter and exit tools are disabled together (`:167-178`) — to avoid "you can enter but cannot leave."

**`EnterWorktree`** — create a temporary git worktree as an isolated workspace.
- Parameters: `name` (slug-validated, optional).

**`ExitWorktree`** — leave a worktree.
- Parameters: `action: keep | remove`; uncommitted changes require an explicit `discard_changes: true`.
- Details: `isDestructive` is decided from the input (section 8.2); a **session-scoped scope guard** only recognizes worktrees created by this session (`:177-182`) — this tool cannot be used to touch another session's workspace. Enablement: `isWorktreeModeEnabled()` is currently always true (`tools.ts:225`).

### 8.13 Scheduling and triggers (5)

**`Sleep`** — wait a specified duration.
- Details: does not occupy a shell process; can be interrupted; its prompt recommends the model prefer it over `Bash(sleep)`, and reminds "each wake-up is one API call; the prompt cache expires in 5 minutes" (`SleepTool/prompt.ts:3`) — even sleeping has to count the cache bill. Enablement: `feature('PROACTIVE') || feature('KAIROS')` (`tools.ts:25-28`).

**`CronCreate` / `CronDelete` / `CronList`** — register a cron timed task that enqueues a prompt when due. 🐢
- CronCreate parameters: `cron` (5-field local time), `prompt`, `recurring` (false = one-shot), `durable` (true persists to `.claude/scheduled_tasks.json`, surviving restarts).
- Example: `{"cron": "*/30 * * * *", "prompt": "检查 CI 状态", "recurring": true, "durable": true}`.
- Enablement: `feature('AGENT_TRIGGERS')` (`tools.ts:29-35`).

**`RemoteTrigger`** — manage remote triggers.
- Parameters: `action: list | get | create | update | run` + `trigger_id` + `body`.
- Enablement: `feature('AGENT_TRIGGERS_REMOTE')` (`tools.ts:36-38`).

### 8.14 LSP and internal/test tools (3)

**`LSP`** — call a language server for semantic code navigation.
- Parameters: `operation` (goToDefinition/findReferences/hover/documentSymbol/workspaceSymbol/goToImplementation/prepareCallHierarchy/incomingCalls/outgoingCalls), `filePath`, `line`, `character` (1-based).
- Example: `{"operation": "findReferences", "filePath": "/repo/src/api.ts", "line": 42, "character": 10}`.
- Enablement: `ENABLE_LSP_TOOL` env (`tools.ts:224`). Grep is "text-level" search; LSP is "semantic-level" search — it knows who is a real function reference and who is only a same-named string.

**`StructuredOutput`** — in non-interactive (SDK/headless) mode, have the model emit a structured result matching a caller-supplied JSON Schema, Ajv-validated (`SyntheticOutputTool/:20`).
- Enablement: non-interactive sessions only (`:24-28`) — when a program calls Claude, what it wants is an answer it can `JSON.parse` directly, not prose.

**`TestingPermissionTool`** — a test-only mock of the permission dialog, registered only when `NODE_ENV==='test'` (`tools.ts:244`) — automated tests have nobody to click a dialog, so this pretends to be the user.

Additionally, `tools.ts` cites a batch of tools whose **files are missing from this snapshot** (feature-gated experiments / internal): TungstenTool, SuggestBackgroundPRTool, MonitorTool, SendUserFileTool, PushNotificationTool, SubscribePRTool, VerifyPlanExecutionTool (`CLAUDE_CODE_VERIFY_PLAN=true`), OverflowTestTool, CtxInspectTool, TerminalCaptureTool, WebBrowserTool, SnipTool, ListPeersTool, WorkflowTool — knowing they exist is enough; the field guide inventories what the snapshot actually has.

### 8.15 ToolSearch: when there are many tools, announce the dishes first, then pass the menu

40-odd tools, each with a spec sheet (name + description + JSON Schema) that costs tokens, **sent in full every turn** is a huge bill. ToolSearch's solution (`defer_loading`):

> **Day to day, give the model only a "dish-name list" (tool names, no schema); whichever dish the model wants to eat, it first calls ToolSearch to look it up, and only then is the full "menu" (full schema) passed.**

```mermaid
sequenceDiagram
    participant 软件
    participant 模型
    软件->>模型: 每轮请求：全部工具名清单（defer_loading:true，无 schema）<br/>+ ToolSearch 工具本体（说明书齐全）
    Note over 模型: 想读文件，但不知道 Read 的参数格式
    模型-->>软件: tool_use: ToolSearch {query: "select:Read,Edit"}
    软件-->>模型: &lt;functions&gt; 块：Read 和 Edit 的完整 JSONSchema
    模型-->>软件: tool_use: Read {file_path: "..."}（现在会用了）
```

A few key questions:

**Who is deferred?** `isDeferredTool()` (`ToolSearchTool/prompt.ts:62-108`): **every MCP tool is deferred**; of the built-ins, the 26 marked `shouldDefer: true` are deferred (NotebookEdit, AskUserQuestion, WebFetch, WebSearch, EnterPlanMode, ExitPlanMode, TodoWrite, TaskCreate/Get/Update/List, TaskOutput, TaskStop, SendMessage, TeamCreate/Delete, CronCreate/Delete/List, ReadMcpResource, ListMcpResources, EnterWorktree, ExitWorktree, Config, LSP, RemoteTrigger). Two exemptions: those marked `alwaysLoad: true` (MCP tools can declare exemption via `_meta['anthropic/alwaysLoad']`, `client.ts:1785`) and ToolSearch itself — it can never be late, or nobody can look up the other tools.

**How is it switched?** Env `ENABLE_TOOL_SEARCH`: `'tst'` (default, always defer), `'tst-auto'` (enable only over a threshold, `checkAutoThreshold` at `utils/toolSearch.ts:444-467`), `'standard'` (off). Haiku does not support `tool_reference` and auto-degrades off this set (`:204,239-252`). At request time, deferred tools are sent to the API tagged `defer_loading: true` (`utils/api.ts:211-223`).

**Two lookup styles** (`ToolSearchTool.ts:132-216`):
- **Exact select**: `{"query": "select:Read,Edit"}` — name which ones, get the schema directly;
- **Keyword search**: `{"query": "slack send"}` — score-match by word: MCP tools are tokenized on `mcp__server__action`, built-ins on CamelCase; e.g. `slack send` can hit `mcp__slack__send_message`.

Registration: ToolSearch is only put on the tool list if `isToolSearchEnabledOptimistic()` is true (`tools.ts:247-249`).

### 8.16 MCPTool: the "naturalization" wrapper for foreign tools

`tools/MCPTool/MCPTool.ts` is only a placeholder template (`name: 'mcp'`, methods annotated "Overridden in mcpClient.ts"). Real wrapping happens in `fetchToolsForClient` at `services/mcp/client.ts:1743-1832`:

1. send `tools/list` to the MCP server and get the tool list;
2. each tool is expanded/overridden as `{...MCPTool, name: fullyQualifiedName, ...}` (`:1769-1773`), cloning a "local edition";
3. **naming rule**: `mcp__<normalized-server-name>__<tool-name>` (`mcpStringUtils.ts:48`), e.g. `mcp__github__create_issue`. An SDK server can set `CLAUDE_AGENT_SDK_MCP_NO_PREFIX` to skip the prefix (`:1760-1763`);
4. **safety metadata comes from MCP annotations**: the server's self-reported annotations are mapped onto the three flags of section 8.2 (`:1795-1809`); `mcpInfo` lets permission rules match a whole server;
5. overlong descriptions are truncated at `MAX_MCP_DESCRIPTION_LENGTH` (`:1789-1794`); permissions default to passthrough, with a suggestion that the user add a `localSettings` allow rule.

```mermaid
flowchart LR
    subgraph SR["MCP 服务器自报家门（annotations）"]
        A["readOnlyHint"]
        B["destructiveHint"]
        C["openWorldHint"]
    end
    A --> D["isReadOnly +<br/>isConcurrencySafe"]
    B --> E["isDestructive"]
    C --> F["isOpenWorld"]
    D --> G["包装好的 MCPTool<br/><b>与内置工具一视同仁</b>：<br/>同样 schema 校验、同样五关权限裁决"]
    E --> G
    F --> G
```

Three accompanying "sidekick" tools:

- **`McpAuthTool`** (`client.ts:63`): when a server needs OAuth, dynamically register an `mcp__<server>__authenticate` tool to walk you through authorization (`client.ts:2318,2331`);
- **`ListMcpResourcesTool`**: list MCP resources (parameter `server?`);
- **`ReadMcpResourceTool`**: read an MCP resource (parameters `server` + `uri`).

The last two are registered at the end of `getAllBaseTools` (`tools.ts:245-246`) but added on demand by `getTools()` as `specialTools` (`:301-307`).

> 🔍 **Source pointers**: `src/tools.ts:193-251,262-269,271-327,345-367` (register/filter/sort) ｜ `src/Tool.ts:750-761` (three-flag defaults) ｜ `src/tools/BashTool/BashTool.tsx:227-259,424,434-465` (Bash parameters / hidden fields / compound-command split) ｜ `src/tools/FileEditTool/FileEditTool.ts:281` (read before write) ｜ `src/tools/ToolSearchTool/prompt.ts:62-108`, `ToolSearchTool.ts:132-216` (defer list and search scoring) ｜ `src/services/mcp/client.ts:1743-1832` (MCPTool wrapping)

---

## 9. A field guide to slash commands: how 60+ shortcuts work

Chapter 4 said: input starting with `/` is routed to the command system. This chapter spreads the command system out: first recap the three command types, then a master table of 60+ commands, then pick 8 of the most representative and take them down to source, and finally teach you to write your own.

### 9.1 Recap: three command types, three fates (plus two mechanisms)

A command object has three types (`types/command.ts`), with sharply different fates:

```mermaid
flowchart TD
    A["你敲下 /xxx 回车"] --> B["parseSlashCommand 解析"]
    B --> C["findCommand 精确匹配<br/><i>commands.ts:688：名字 / 全名 / 别名<br/>查不到 → Unknown command，绝不瞎猜</i>"]
    C --> D{"什么类型？"}
    D -->|"local-jsx 🖥️"| E["渲染终端 UI（设置面板、选择器）<br/><b>不发 AI</b>"]
    D -->|"local ⚙️"| F["执行本地函数（/clear、/cost）<br/><b>不发 AI</b>"]
    D -->|"prompt 📝"| G["展开成一段预制提示词<br/><b>发给 AI</b>"]
```

Two easy-to-miss mechanisms:

- **Lazy load**: a command's `index.ts` stores only metadata (name, description, type); the body is `load: () => import(...)` dynamically loaded — e.g. `/insights` is 115KB of implementation; if you never use it, it never occupies memory. Same philosophy as chapter 3's `--version` fast path: do less at startup.
- **Registration order**: the `COMMANDS()` array (`commands.ts:258`) exposes about 60 commands; additionally `INTERNAL_ONLY_COMMANDS` (`:225`) is internal-only. `loadAllCommands()` (`:449`) merges in the order **bundled skills → built-in plugin skills → directory skills → workflows → plugin commands → plugin skills → hard-coded built-in commands**; `findCommand` takes the first match in array order — so a same-named user/plugin skill **ranks ahead of** a hard-coded built-in command, and you can "override" a built-in.

### 9.2 Master table: 60+ commands at a glance

Type icons: ⚙️ local (local function) ｜ 🖥️ local-jsx (terminal UI) ｜ 📝 prompt (send to the model). Marked 🚪 are feature-gated / internal-only.

**① Session management**

| Command | Type | One-sentence description |
|---|---|---|
| /clear (aliases reset/new) | ⚙️ | Clear conversation history and start a new session — in substance a "soft restart" (section 9.3) |
| /compact | ⚙️ | Clear history but keep a summary; can take an argument as a custom summary instruction |
| /resume (alias continue) | 🖥️ | Resume a previous conversation: pop a picker or search by ID/title |
| /branch (alias fork) | 🖥️ | Fork a conversation branch at the current node |
| /rewind (alias checkpoint) | ⚙️ | Roll code and/or conversation back to a previous checkpoint |
| /rename | 🖥️ | Rename the current conversation; with no argument, auto-generate a name (immediate) |
| /export | 🖥️ | Export the conversation as .txt or copy to the clipboard |
| /copy | 🖥️ | Copy the last reply; `/copy N` takes the Nth from the end |
| /exit (alias quit) | 🖥️ | Leave the REPL (immediate) |
| /session (alias remote) | 🖥️ | Show the remote-session URL and QR code |
| /btw | 🖥️ | Ask a side-quest question quickly, without interrupting the main conversation (immediate) |
| /plan | 🖥️ | Enter plan mode or view the current plan |
| /share /summary /teleport /onboarding /backfill-sessions | 🚪 | Five internal stubs; empty shells in the external build |

**② Model and configuration**

| Command | Type | One-sentence description |
|---|---|---|
| /model | 🖥️ | Set the main-loop model; no argument pops a picker, with an argument switches directly |
| /effort | 🖥️ | Set the model's effort grade low/medium/high/max/auto |
| /fast | 🖥️ | Toggle fast mode |
| /advisor | ⚙️ | Set/unset the advisor model |
| /config (alias settings) | 🖥️ | Open the settings panel |
| /output-style | 🖥️ | Deprecated; tells you to use /config |
| /statusline | 📝 | Dispatch a statusline-setup sub-agent to configure the status bar |
| /theme | 🖥️ | Change the terminal theme |
| /color | 🖥️ | Set this session's prompt-bar color (immediate) |
| /vim | ⚙️ | Toggle Vim/Normal editing mode |
| /keybindings | ⚙️ | Open or create the keybinding config file |
| /terminal-setup | 🖥️ | Install the Shift+Enter newline keybinding |
| /voice | ⚙️🚪 | Toggle voice mode (feature-gated) |
| /brief | 🖥️🚪 | Toggle ultra-brief reply mode (KAIROS, immediate) |

**③ Context and memory**

| Command | Type | One-sentence description |
|---|---|---|
| /memory | 🖥️ | Pick a memory file (the CLAUDE.md family) and open it in an editor |
| /init | 📝 | Scan the codebase, generate/improve CLAUDE.md (the new version can also generate skills and hooks) |
| /context | 🖥️ | A color grid visualizing current context occupancy |
| /add-dir | 🖥️ | Add a new working directory |
| /insights | 📝 | Analyze all sessions and generate an HTML insights report (115KB, the typical lazy-load) |
| /files | ⚙️🚪 | List every file in context (internal) |

**④ Tasks and code**

| Command | Type | One-sentence description |
|---|---|---|
| /review | 📝 | Review a PR (locally runs `gh pr diff` and has the model write a review) |
| /ultrareview | 🖥️ | Run a 10–20 minute deep bug hunt on Claude Code on the web |
| /security-review | 📝 | Security-review uncommitted changes |
| /commit | 📝🚪 | Analyze changes and create a git commit (internal-only in this snapshot) |
| /commit-push-pr | 📝🚪 | Commit, push, and open a PR in one shot (internal) |
| /pr-comments | 📝 | Pull PR comments (a transitional shell already moved to a plugin) |
| /diff | 🖥️ | View uncommitted changes and per-turn diffs |
| /tasks (alias bashes) | 🖥️ | List and manage background tasks |
| /agents | 🖥️ | Manage sub-agent config |
| /skills | 🖥️ | List available skills |
| /hooks | 🖥️ | View hook config (immediate) |
| /mcp | 🖥️ | Manage MCP servers (immediate) |
| /plugin (aliases plugins/marketplace) | 🖥️ | Manage plugins and the marketplace (10+ sub-UIs, immediate) |
| /reload-plugins | ⚙️ | Apply pending plugin changes |
| /ultraplan | 🖥️🚪 | Draft a 10–30 minute advanced plan in the cloud (gated) |
| /ide | 🖥️ | Manage IDE integration |
| /sandbox | 🖥️ | Sandbox on/off and config (immediate) |

**⑤ Permissions and security**

| Command | Type | One-sentence description |
|---|---|---|
| /permissions (alias allowed-tools) | 🖥️ | Manage allow/ask/deny rules (including "retry a denied operation") |
| /privacy-settings | 🖥️ | Privacy settings |
| /doctor | 🖥️ | Diagnose the install and settings |
| /login | 🖥️ | Log in / switch Anthropic account (OAuth2+PKCE) |
| /logout | 🖥️ | Log out |

**⑥ Remote and collaboration**

| Command | Type | One-sentence description |
|---|---|---|
| /desktop (alias app) | 🖥️ | Continue the session in Claude Desktop |
| /mobile (aliases ios/android) | 🖥️ | Show a QR code to download the mobile App |
| /chrome | 🖥️ | Claude in Chrome settings |
| /install-github-app | 🖥️ | Configure Claude GitHub Actions (full wizard) |
| /install-slack-app | ⚙️ | Install the Slack app |
| /remote-env | 🖥️ | Configure a remote environment |
| /web-setup | 🖥️ | Set up Claude Code on the web |
| /bridge | 🖥️🚪 | Remote Control bridge (gated) |
| /feedback (alias bug) | 🖥️ | Submit product feedback |

**⑦ Info and help**

| Command | Type | One-sentence description |
|---|---|---|
| /help | 🖥️ | Help and every command (a "mirror" that renders the runtime command registry) |
| /status | 🖥️ | Version, model, account, API connectivity (immediate) |
| /cost | ⚙️ | Session total cost and duration (a purely local ledger) |
| /usage | 🖥️ | Plan usage limits |
| /extra-usage | 🖥️ | Extra-usage config after the quota is exhausted |
| /stats | 🖥️ | Usage stats and activity |
| /release-notes | ⚙️ | Version changelog |
| /upgrade | 🖥️ | Upgrade to the Max plan |
| /passes | 🖥️ | Share a free week with a friend |
| /stickers | ⚙️ | Order stickers (easter egg) |
| /think-back | 🖥️🚪 | Year in review (gated) |
| /heapdump | ⚙️🚪 | Dump the JS heap to the desktop (hidden) |

Three extra pieces of trivia: `createMovedToPluginCommand.ts` is a compatibility-shell factory for "commands that moved to plugins" (`/pr-comments`, `/security-review` use it); `REMOTE_SAFE_COMMANDS` (`commands.ts:619`) says remote mode keeps only 17 purely local UI commands; `BRIDGE_SAFE_COMMANDS` (`:651`) says phone/web can remotely trigger 6: `/compact`, `/clear`, `/cost`, `/summary`, `/release-notes`, `/files`.

### 9.3 Deep dives (8 representatives)

#### /clear — not just "clear the screen"; an 8-step soft restart

Entry `clear/clear.ts:4` → `clearConversation()` at `conversation.ts:49`:

1. First run SessionEnd hooks (1.5s timeout, `:68-74`);
2. Report `tengu_cache_eviction_hint`, telling the server to evict the prompt cache (`:77-85`);
3. Background agent tasks and main-session tasks (Ctrl+B) **survive**; foreground tasks are all killed (`:93-107, 135-166`);
4. `setMessages([])` clears messages; update `conversationId` to force a redraw (`:109-122`);
5. `clearSessionCaches()` clears session caches (keeps surviving agents' private state), resets cwd, clears file-read cache / discovered skills / nested-memory paths (`:124-132`);
6. Reset AppState: file-history snapshots, attribution, MCP clients all reset (triggering reconnect); keep `pluginReconnectKey` (`:168-191`);
7. `regenerateSessionId({setCurrentAsParent:true})` issues a new session ID (old ID recorded as parent, for lineage analysis); surviving tasks' TaskOutput symlinks point at the new session directory (`:203-224`);
8. Re-persist mode/worktree state; run SessionStart hooks to inject messages (`:230-250`).

In one sentence: **a "soft restart" without restarting the process**, accounting for lineage analysis, surviving background tasks, and the hook lifecycle.

#### /compact — dispatch a fork agent to write a summary

Entry `compact/compact.ts:40`, the flow:

1. Project away snipped messages; a user argument becomes a custom summary instruction (`:46-52`);
2. With no custom instruction, first try session-memory compaction (a cheaper incremental path, `:57-83`);
3. Traditional path: first microcompact (in-place compact of old tool results), then `compactConversation()`;
4. PreCompact hooks can inject extra summary instructions; on prompt-too-long, drop the oldest turns and retry, up to `MAX_PTL_RETRIES` times (`:460-491`);
5. **Fork-agent cache-sharing path** (`:1151-1203`): `runForkedAgent` dispatches a sub-agent that runs only 1 turn (`maxTurns: 1`) with no tools, carrying a system prompt / tools / message prefix **identical to the main thread** so it hits the prompt cache — the source comment states the motive: the old path had a 98% cache miss, wasting about 38B tokens a day. On failure, fall back to ordinary streaming (`:1280-1326`);
6. Summary template `BASE_COMPACT_PROMPT` (`services/compact/prompt.ts:61`): 9 fixed sections (Primary Request / Key Technical Concepts / Files and Code Sections / Errors and fixes / Problem Solving / All user messages / Pending Tasks / Current Work / Optional Next Step);
7. `buildPostCompactMessages` rebuilds the message sequence: compact boundary + summary + kept recent messages.

In one sentence: it does not delete messages; it "uses a cache-friendly fork sub-agent to write the old conversation into a structured summary, then rebuilds the session with that summary as the new foundation."

#### /init — an extra-long "interview script"

Entry `commands/init.ts:226` (prompt type). The command itself **does not scan a single line** — it only returns a prompt and lets the main model do the work:

- Old version (`:6-26`): have the model analyze the codebase and write CLAUDE.md, with a clear "should write" (build/lint/test commands, high-level architecture) and "should not write" (obvious advice, a per-file inventory);
- New version (`:28-224`): an 8-stage script — P1 AskUserQuestion about building project/personal files → P2 dispatch a sub-agent to survey the codebase → P3 interview the user to fill blinds → P4/5 write files (every line passes a "if we deleted this, would Claude make a mistake?" test) → P6 create skills → P7 create hooks → P8 summarize.

In one sentence: **the extreme form of a prompt-type command — "the command *is* the script."** That also answers "why `/init` costs tokens": it is in essence a long prompt sent to the model.

#### /login — PKCE + loopback listener, dual channel

Entry `login/login.tsx:19` → `ConsoleOAuthFlow` (`components/ConsoleOAuthFlow.tsx`):

```mermaid
sequenceDiagram
    participant CLI as Claude Code CLI
    participant 浏览器
    participant AS as Anthropic 授权服务器

    CLI->>CLI: ① 生成 PKCE code_verifier/code_challenge + state<br/><i>services/oauth/client.ts:47-87</i>
    CLI->>CLI: ② 起临时 localhost HTTP 服务器监听 /callback<br/><i>auth-code-listener.ts:10-16</i>
    CLI->>浏览器: ③ 打开授权 URL（带 code_challenge）
    浏览器->>AS: 用户登录并授权
    AS-->>浏览器: 302 跳回 localhost:PORT/callback?code=...
    浏览器-->>CLI: ④ 回环通道自动捕获 code，校验 state
    Note over CLI: 3 秒没跳转？显示手动粘贴框（:157-181）<br/>——SSH 无浏览器环境也能完成
    CLI->>AS: ⑤ exchangeCodeForTokens 换 token（:103-109）
    AS-->>CLI: access_token + refresh_token
    CLI->>CLI: ⑥ installOAuthTokens 存安全存储（cli/handlers/auth.ts:44）<br/>⑦ 收尾：重建客户端、剥旧 thinking 签名、<br/>resetCostState、authVersion+1（login.tsx:25-55）
```

In one sentence: standard OAuth2+PKCE, **dual channel** (loopback automatic / manual paste) so an SSH environment with no browser can still finish login. Chapter 11 will cover where the token is stored and how expiry is renewed.

#### /resume — conversation archaeologist; across projects, prefer a command over crossing the boundary

Entry `resume/resume.tsx`: with no argument, `getWorktreePaths` + `loadSameRepoMessageLogs` + `filterResumableSessions` render `LogSelector` (supports semantic search); after a pick, `checkCrossProjectResume` decides — **same-repo worktree resumes directly; a different project is not resumed**, instead a `claude --resume ...` command is concatenated and copied to the clipboard (`:145-164`), for you to run in the new directory yourself. With an argument, first exact UUID match then search by title; what it reads is the `~/.claude/projects/<project>/<sessionId>.jsonl` transcript file.

Why "prefer a command over crossing the boundary" across projects? A session is bound to a working directory and project context; switching projects in place would make one session serve two repos at once — the security boundary and the memory system would both go wrong. Giving you a command to start over on the other side is the most restrained and safest move.

#### /permissions — five tabs + retry a denied operation

Entry `permissions/permissions.tsx`: `PermissionRuleList` has **five tabs** (recent/allow/ask/deny/workspace); rules come from layered settings and are tagged with their source (user/project/local); `applyPermissionUpdate` takes effect in memory + `persistPermissionUpdate` writes the file; it also detects "shadowed rules" (you added a rule that can never fire, and it reminds you). The most interesting piece is `onRetryDenials`: wrap recently denied commands as `createPermissionRetryMessage` and stuff them back on the queue **so the model retries** — after you allow an operation on the permissions page, you do not have to restate the task; the model picks it up and tries again.

#### /cost — a purely local ledger

Entry `cost/cost.ts:6`: subscribers see a subscription hint; API users `formatTotalCost()` (`cost-tracker.ts:228`) output total cost/duration/lines added-deleted/token usage grouped by model (including cache read/write). **Data all comes from in-process accumulators; no network request at all** — typical of a `local` command: a pure function formatting in-memory counters. So `/cost` is always instant, and costs no tokens.

#### /commit — the textbook prompt command (internal-only in this snapshot)

Entry `commands/commit.ts:76`; it takes the three tricks of a prompt-type command to the extreme:

```mermaid
flowchart TD
    A["你敲 /commit"] --> B["提示词里嵌四处 !`...` 预执行：<br/>git status · git diff HEAD ·<br/>git branch --show-current · git log --oneline -10<br/><i>commit.ts:16-19</i>"]
    B --> C["本地先执行这 4 条命令<br/>输出替换进提示词文本"]
    C --> D["展开后的完整提示词发给模型：<br/>「这是仓库现状，请按安全协议提交」"]
    D --> E["安全协议约束模型：<br/>不改 git config · 不跳 hooks · 不 amend<br/>不提交疑似密钥"]
    D --> F["allowedTools 白名单：<br/>只放行 git add/status/commit"]
    D --> G["两段式任务：先起草聚焦 why 的 message<br/>再 HEREDOC 单条消息 stage+commit+署名"]
```

In one sentence: **pre-execute to get the current state, a whitelist to govern the hands, a safety protocol to govern the floor** — every move a custom command can learn is on this page.

### 9.4 A custom-command tutorial: write your own `/review-fix`

However strong the built-ins, nothing is as handy as one you wrote. Custom commands have two formats and four load locations.

**Load locations and formats** (`skills/loadSkillsDir.ts:638-803`):

| Location | Format | Notes |
|---|---|---|
| `~/.claude/commands/*.md` | old format (`:566` tagged `commands_DEPRECATED`) | a single file is a command |
| `~/.claude/skills/<name>/SKILL.md` | new format | directory + SKILL.md, can carry attachment files |
| `<project>/.claude/commands/*.md` | old format | project-level, shared with the team via git |
| `<project>/.claude/skills/<name>/SKILL.md` | new format | directory format only; a lone `.md` is not treated as a skill (`:424-428`) |

(There is also the enterprise managed-policy directory and `--add-dir` extra directories; plugin commands go through `loadPluginCommands.ts:414`.)

**Namespace**: a subdirectory becomes a colon prefix — `.claude/commands/frontend/lint.md` → `/frontend:lint` (`buildNamespace :523-552`); plugin commands carry a `plugin:command` prefix. Same names: first `realpath` wins (`:725-763`).

**Template capabilities** (`getPromptForCommand :344-399`):

1. **Argument substitution** (`utils/argumentSubstitution.ts`): `$ARGUMENTS` all arguments; `$ARGUMENTS[0]`/`$0` the Nth; after frontmatter `arguments: ticketId priority` you can use the named `$ticketId`; with no placeholder at all, automatically append `\n\nARGUMENTS: <args>` at the end (`:140-142`).
2. **Shell pre-execution** (`utils/promptShellExecution.ts:69`): two syntaxes, inline `` !`cmd` `` and block-level ` ```! `; **before** sending to the model, executed by BashTool, stdout/stderr substituted into the text; execution first goes through `hasPermissionsToUseTool`; the skill's `allowed-tools` is temporarily merged into always-allow. **Safety red line: skills loaded from MCP always skip shell pre-execution** (`:371-374`) — remote content is untrusted; a network server must not be allowed to run commands on your machine.
3. **Variable injection**: `${CLAUDE_SKILL_DIR}` (the skill's own directory), `${CLAUDE_SESSION_ID}`.
4. **How it runs**: default inline expansion into the current conversation; with frontmatter `context: fork` it runs as a sub-agent (`executeForkedSlashCommand`, `processSlashCommand.tsx:62`), optionally with `agent:` to specify the sub-agent type.

**Frontmatter field cheat sheet** (`parseSkillFrontmatterFields :185-265`):

| Field | Role |
|---|---|
| description | one-sentence description (default extracted from the body) |
| argument-hint / arguments | argument hint / named arguments |
| allowed-tools | whitelist of tools available while the skill runs |
| when_to_use | tell the model which scenes should invoke it |
| model / effort | specify a model (may inherit) / thinking effort |
| disable-model-invocation | humans only; the model cannot invoke |
| user-invocable: false | the user does not see it in the `/` menu |
| context: fork / agent | run in a forked sub-context / specify a sub-agent |
| paths | conditional skill: activated when a matching-path file is read/written |
| shell | which interpreter embedded shell commands use (e.g. powershell) |
| hooks / version / name | skill-level hooks / version / display name |

**A complete example: write a `/review-fix` command.** Need: have Claude review the current git changes against team convention and fix the problems directly. Create `.claude/commands/review-fix.md`:

```markdown
---
description: 审查当前改动并直接修复发现的问题
allowed-tools: Bash(git *), Read, Edit, Grep
argument-hint: [严重级别，如 critical]

# 任务

当前未提交的改动如下：

!`git diff HEAD`

请完成：
1. 按团队规范（CLAUDE.md）审查以上 diff，只报告 $ARGUMENTS 级别以上的问题；
2. 对每个问题直接用 Edit 修复；
3. 修复后运行 !`git status --short` 确认改动范围没有越界。

安全要求：不要提交，不要推送。
```

After you type `/review-fix critical`, the full lifecycle is (`getMessagesForPromptSlashCommand` at `processSlashCommand.tsx:827`): register skill hooks → `addInvokedSkill` records → generate a command-metadata message + `isMeta` expanded body (at this point the two `` !`...` `` pre-executions run) → parse `@file` attachments → attach a `command_permissions` attachment → `shouldQuery: true` send to the model.

### 9.5 Immediate commands: why some commands do not queue

While the model is busy writing code, an ordinary message you type goes on the queue (chapter 4). But a **purely local UI command** like `/theme`, `/status` waiting for the model to finish feels terrible — you only wanted to change the theme. Hence `immediate: true`: on a hit, `load().call()` runs directly, **bypassing the queue pipeline** (`REPL.tsx:3158-3184`), and reports `tengu_immediate_command_executed`.

- **Which commands have it**: `/btw`, `/color`, `/exit`, `/hooks`, `/mcp`, `/plugin`, `/rename`, `/sandbox`, `/status`, `/brief`; `/model`, `/effort`, `/fast` are conditionally enabled (`shouldInferenceConfigCommandBeImmediate`, `utils/immediateCommand.ts:11`).
- **A keybinding trigger is always immediate**: whatever command a shortcut evokes, it is treated as immediate — the keybinding itself is an explicit signal of "I want this now."
- **Bridge safety whitelist**: commands coming from phone/web have a separate `isBridgeSafeCommand` (`commands.ts:672`) gate: `local-jsx` is always refused (the remote has no terminal to render a picker), `prompt` is admitted, `local` needs a whitelist. This defense comes from the lesson of PR #19134: the phone used to be able to trigger a local Ink picker dialog — the dialog appeared in a terminal you could not see, and the session hung.

> 🔍 **Source pointers**: `src/commands.ts:225,258,449,688,619,651` (register/lookup/safety lists) ｜ `src/utils/processUserInput/processSlashCommand.tsx:525,62,827` (dispatch/fork/expand) ｜ `src/commands/conversation.ts:49` (`/clear` soft restart) ｜ `src/commands/commit.ts:16-19,76` (pre-execute + whitelist) ｜ `src/skills/loadSkillsDir.ts:185-265,344-399,523-552,638-803` (frontmatter/template/namespace/load locations) ｜ `src/utils/promptShellExecution.ts:69,371-374` (pre-execute and the MCP red line) ｜ `src/screens/REPL.tsx:3158-3184` (immediate)

---

## 10. Ecosystem components: what plugins, skills, and MCP each are

However rich the built-in tools and commands, they cannot hold the world's demand. Claude Code left three doors open to the outside: **plugins** (package and distribute a whole set of capabilities), **skills** (spec sheets pulled on demand), **MCP** (a standard interface to plug in external services). The three often appear packaged together, but they solve completely different problems — section 10.4 has a comparison table.

### 10.1 Plugins: a "capability pack" installed all at once

**Metaphor**: a plugin is like a VS Code extension. A single `.claude/commands/xxx.md` file is too scattered; you cannot conveniently share and version a whole workflow. A plugin **packs slash commands, skills, subagent definitions, hooks, MCP server config, and output styles into one directory**, all installed in one shot.

**Directory convention** (file-header comments at `utils/plugins/pluginLoader.ts:16-28`; `createPluginFromPath()` at `:1357`):

```mermaid
flowchart TD
    subgraph 插件目录["📦 一个插件 = 一个目录"]
        M["plugin.json<br/>清单文件（名字/版本/描述）"]
        C["commands/<br/>斜杠命令 *.md"]
        A["agents/<br/>subagent 定义"]
        S["skills/<br/>技能 SKILL.md"]
        H["hooks/hooks.json<br/>钩子脚本配置"]
        O["output-styles/<br/>输出样式 *.md"]
    end
    M --> L["createPluginFromPath 加载"]
    C --> L
    A --> L
    S --> L
    H --> L
    O --> L
    L --> P["commands/agents/skills/output-styles<br/>四个可选目录<b>并行探测</b>是否存在<br/><i>pluginLoader.ts:1392-1398</i>"]
```

**A marketplace = a git repo + `marketplace.json`**. A marketplace is essentially a git repo (or a URL/NPM package) containing a `marketplace.json` catalog that registers which plugins it provides. The official marketplace is the GitHub repo `anthropics/claude-plugins-official` (`utils/plugins/officialMarketplace.ts:13-27`). The full flow:

1. `/plugin marketplace add anthropics/claude-plugins-official` → `addMarketplaceSource()` (`marketplaceManager.ts:1782`);
2. `gitClone` uses a **partial clone + sparse checkout** to clone the repo into `~/.claude/plugins/marketplaces/` (`:803`, `1034 reconcileSparseCheckout`) — a marketplace repo may have dozens of plugins; a sparse clone only pulls the few you install, **saving bandwidth and disk**;
3. `/plugin install feature-dev@claude-plugins-official` → `installPluginFromMarketplace()` (`pluginInstallationHelpers.ts:506`) lands it;
4. On next startup `loadAllPlugins()` (`pluginLoader.ts:3096`) loads uniformly; plugin commands appear in the `/` list, source-tagged as plugin.

**Load priority**: the command-merge order of chapter 9 — bundled skills → built-in plugin skills → directory skills → workflows → plugin commands → plugin skills → hard-coded built-in commands (`commands.ts:447-470`). `findCommand` takes the first match, so **a plugin command can override a hard-coded built-in**.

**Built-in plugins**: shipped with the CLI, toggleable in the `/plugin` UI (`plugins/builtinPlugins.ts:21-32`), IDs of the form `{name}@builtin`, no filesystem path (`path: 'builtin'` sentinel, `:85`); on/off state persisted to settings `enabledPlugins` (`:71-76`). The UI lives in `commands/plugin/` (`ManagePlugins.tsx`, `BrowseMarketplace.tsx`, `AddMarketplace.tsx`, and 10+ other sub-UIs).

### 10.2 Skills: spec sheets pulled on demand

**Metaphor**: stuffing every tool's full manual into the drawer (the system prompt) is too heavy. So Claude Code only pastes a one-page **table of contents** on the drawer (skill name + one-sentence description); when the model decides it needs a particular manual, it pulls the whole book out via the Skill tool. That is **progressive disclosure**.

```mermaid
sequenceDiagram
    participant 软件
    participant 模型
    Note over 软件: 第一阶段：只注入清单（省 token）
    软件->>软件: getSkillListingAttachments<br/><i>attachments.ts:2661</i><br/>所有技能的 name/description/whenToUse<br/>打包成一个 skill_listing 附件，按 token 预算裁剪
    软件->>模型: 请求里夹带技能清单（每个 agent 只发一次，增量补发 :2698-2732）
    Note over 模型: 用户说"把 report.pdf 第 3 页的表抠出来"<br/>→ 清单里有 pdf 技能，决定使用
    模型-->>软件: tool_use: Skill {skill: "pdf", args: "..."}
    Note over 软件: 第二阶段：此刻才加载全文
    软件->>软件: getPromptForCommand（loadSkillsDir.ts:344-399）：<br/>拼入 Base directory + SKILL.md 完整正文<br/>替换 $ARGUMENTS/${CLAUDE_SKILL_DIR}/${CLAUDE_SESSION_ID}<br/>执行内嵌 !`shell`（MCP 技能除外）
    软件-->>模型: 完整技能说明书（含 scripts/extract.py 的存在）
    模型-->>软件: tool_use: Bash 跑提取脚本
```

**Key details**:

- **Format**: only the `skill-name/SKILL.md` directory layout is recognized; a lone `.md` file is not treated as a skill (`loadSkillsDir.ts:424-428`).
- **Four load locations** (`:638-714`): enterprise-managed → user-level `~/.claude/skills` → project-level `.claude/skills` (including `--add-dir`) → the legacy `/commands/` directory.
- **Frontmatter fields**: name, description, when_to_use, allowed-tools, argument-hint/arguments, version, model, effort, disable-model-invocation, user-invocable, hooks, context: fork, agent, shell, paths (parse entry `parseSkillFrontmatterFields :185-265`; see the field table in section 9.4).
- **Conditional skills (`paths`)**: `parseSkillPaths :159-178` supports gitignore-style path filtering — activated only when a matching-path file is read/written (from `activateConditionalSkillsForPaths :997`). E.g. a skill with `paths: ["**/*.py"]` only appears in the listing when a Python file shows up.
- **Dynamic discovery**: while a session reads/writes files, nested `.claude/skills` are discovered by walking up from the file path (`discoverSkillDirsForPaths :861-915`), skipping gitignored directories; a skill in a deeper directory overrides a same-named shallower one (`:944-951`).
- **Built-in bundled skills**: compiled into the binary (`skills/bundledSkills.ts:53`), directory `skills/bundled/`: `batch`, `claudeApi`, `claudeInChrome`, `debug`, `keybindings`, `loop`, `loremIpsum`, `remember`, `scheduleRemoteAgents`, `simplify`, `skillify`, `stuck`, `updateConfig`, `verify`. Skills with attachments unpack reference files to disk only on first invoke, with `O_NOFOLLOW|O_EXCL` against symlink attacks (`:176-193`).
- **Relation to the old commands**: the old format `.claude/commands/*.md` is tagged DEPRECATED and is still compatible under the same load logic; **the safety red line** once more — skills from MCP never execute embedded shell pre-execution (`:372-374`); remote content is untrusted.

**Example: write your own skill.** Create `~/.claude/skills/weekly-report/SKILL.md`:

```markdown
---
name: weekly-report
description: 汇总本周 git 提交生成中文周报
when_to_use: 用户要求写周报、周总结时
allowed-tools: Bash(git *), Read, Write
---

# 周报生成流程

1. 运行 `git log --since="7 days ago" --pretty=format:"%s%n%b"` 收集提交；
2. 按功能模块分组，每组提炼一句"本周进展"；
3. 参照 ${CLAUDE_SKILL_DIR}/template.md 的格式，写入 weekly-report.md。
```

From next week on, you say "help me write the weekly report," and the model finds this spec sheet itself and follows it.

### 10.3 MCP: USB-C for AI

**Metaphor**: MCP (Model Context Protocol) is "USB-C for AI" — any external service (a database, Figma, GitHub, an internal company system) that implements a server per the protocol can be plugged into Claude Code, and its tools, resources, and prompt templates become immediately usable. The client implementation is `services/mcp/client.ts` (3348 lines), based on the official `@modelcontextprotocol/sdk`.

**6 transports** (schema in `services/mcp/types.ts:28-131`):

| Transport | Scene | Implementation |
|---|---|---|
| `stdio` | local subprocess (most common: `npx` starts a process) | `client.ts:950` |
| `sse` | HTTP long-poll push | `client.ts:673,702` |
| `http` | Streamable HTTP | `client.ts:861,900` |
| `ws` / `ws-ide` | WebSocket / embedded in an IDE | `client.ts:734,783` |
| `sdk` | in-process SDK transport | `client.ts:3278` |
| others | special types `sse-ide`, `claudeai-proxy`, etc. | — |

**Config and startup approval**: servers are registered in `.mcp.json` at the project root (enterprises also have `managed-mcp.json`, `services/mcp/config.ts:64-66`):

```json
{
  "mcpServers": {
    "github": { "type": "stdio", "command": "npx", "args": ["-y", "@modelcontextprotocol/server-github"] }
  }
}
```

**Why does an untrusted repo need approval?** Same logic as chapter 3's trust dialog: `.mcp.json` is project-level config, possibly from a strange repo you just `git clone`d — if clone automatically started servers, a malicious repo could automatically run processes on your machine. So at startup each pending server pops an approval dialog (`handleMcpjsonServerApprovals()`, `services/mcpServerApproval.tsx:13-40`); your choice is recorded in settings `enabledMcpjsonServers`/`disabledMcpjsonServers` (from `utils.ts:351`); load time only admits approved ones (`config.ts:1164-1168`). `--dangerously-skip-permissions` and non-interactive mode auto-approve, but that code carries a strict anti-RCE comment (`utils.ts:383-400`).

**Three kinds of capability**: ① **tools** — wrapped as `MCPTool` named `mcp__serverName__toolName` (section 8.16); ② **resources** — browsed/read with ListMcpResourcesTool/ReadMcpResourceTool; ③ **prompts** — turned into Commands (`client.ts:2038-2077`, names likewise `mcp__server__prompt` format). Permission rules support **server-level wildcards**: one rule `mcp__github` or `mcp__github__*` governs the whole server (`utils/permissions/permissions.ts:236-259`).

**Connection state machine** (`types.ts:183-248`): the management UI is the `/mcp` command; the React side is `MCPConnectionManager` + `useManageMCPConnections` for reconnect and on/off:

```mermaid
stateDiagram-v2
    [*] --> pending: 发现 .mcp.json 里的新服务器
    pending --> connected: 你批准 → 连接成功
    pending --> disabled: 你拒绝/禁用
    connected --> failed: 进程崩溃/网络断开
    failed --> connected: 自动重连成功
    connected --> needs_auth: 服务器要求 OAuth
    needs_auth --> connected: mcp__server__authenticate 引导授权完成
    connected --> disabled: /mcp 里手动禁用
    disabled --> connected: /mcp 里手动启用
```

**MCP OAuth**: when a server needs login authorization it enters `needs-auth`, takes the standard OAuth flow (the dynamically registered McpAuthTool walks you through it), and returns to connected after success.

### 10.4 Plugin vs skill vs MCP: one comparison table

| Dimension | Plugin | Skill | MCP |
|---|---|---|---|
| **Problem it solves** | packaging, distributing, and versioning a whole workflow | teaching the model "the SOP for a class of work," loaded on demand to save tokens | plugging in an external service's live data and capabilities |
| **Shape** | one directory (may contain commands/skills/agents/hooks/MCP config) | one SKILL.md (+ optional attachments) | one external server process/service |
| **Who can provide tools** | indirectly (via embedded MCP config) | cannot — it is only a prompt | **can** — `mcp__server__tool` |
| **Trust boundary** | you looked at the marketplace/repo before install | local files are trusted; **skills from MCP are not** (shell pre-execution forbidden) | project `.mcp.json` needs startup approval |
| **Typical source** | a git marketplace (e.g. official `anthropics/claude-plugins-official`) | you write it / a plugin ships it / built-in bundled | a third-party vendor or you build it |
| **Management entry** | /plugin | /skills | /mcp |

One-sentence distinction: **a skill is a "spec sheet," MCP is a "new organ," a plugin is "the gift box that packages the spec sheet and the new organ for sale."**

> 🔍 **Source pointers**: `src/utils/plugins/pluginLoader.ts:1357,1392-1398,3096` (plugin load) ｜ `src/utils/plugins/marketplaceManager.ts:803,1034,1782` (sparse clone / add marketplace) ｜ `src/skills/loadSkillsDir.ts:185-265,424-428,638-714,997` (skill parse/load/conditional activation) ｜ `src/utils/attachments.ts:2661` (skill-listing attachment) ｜ `src/services/mcp/client.ts:1743-1832,2038-2077` (tool/prompt wrapping) ｜ `src/services/mcpServerApproval.tsx:13-40` (startup approval) ｜ `src/services/mcp/types.ts:28-131,183-248` (transports and state machine)

---

## 11. Feature teardown: effort, login, model switching, …

The first ten chapters covered the spine. This chapter teardowns the "feature switches" scattered around: each item in the same pattern — **what it is (a metaphor) → how the source implements it (line numbers) → one example**.

### 11.1 effort / thinking budget: a grade for the model's "drafting time"

**What it is**: before answering, the model can "draft" (thinking), and the draft length is tunable. `effort` is the new-generation unified knob (four grades low/medium/high/max); `thinking budget` is the old-style exact token count; the Claude 4.6 family also supports **adaptive** — the model decides how long to think. Metaphor: the same person, you can have them "answer offhand" (low) or "think it through then speak" (max).

**How the source implements it**:

```mermaid
flowchart TD
    A["resolveAppliedEffort 优先级链<br/><i>utils/effort.ts:152-168</i>"] --> B{"① 环境变量<br/>CLAUDE_CODE_EFFORT_LEVEL？"}
    B -->|有| F["✅ 用它"]
    B -->|无| C{"② 会话状态<br/>（/effort 或 /model 设的<br/>appState.effortValue）？"}
    C -->|有| F
    C -->|无| D["③ 模型默认值"]
    D --> F
    F --> G{"clamp：模型支持吗？<br/><i>modelSupportsEffort :26-50</i>"}
    G -->|"max 仅 Opus 4.6 支持<br/>（:55-69）"| H["不支持 max → 自动降到 high"]
    G -->|支持| I["发给 API：<br/>outputConfig.effort（claude.ts:453-455）<br/>数字档走内部 effort_override（:458-463）"]
    H --> I
```

- Grades are defined in `utils/effort.ts:14-20` (also allows a numeric grade, used internally); the `/effort` command is in `commands/effort/effort.tsx:16-20`; on persist, numeric grades and an external user's `max` are not written to settings (`toPersistableEffort :101-112`).
- **Two shapes of thinking** (`utils/thinking.ts:10-13`): the 4.6 family sends `{type: 'adaptive'}` with no budget; older models send `{type: 'enabled', budget_tokens: N}`, budget = `min(maxOutputTokens-1, configured value ?? model default)` (`claude.ts:1617-1627`). On by default; `MAX_THINKING_TOKENS=0` or settings `alwaysThinkingEnabled: false` can turn it off (`:146-162`).
- **Easter egg**: writing `ultrathink` in the input triggers a highlight and a higher thinking grade (keyword detection at `thinking.ts:29-31` + rainbow render `:60-86`), double-gated by feature flag `ULTRATHINK` + GrowthBook `tengu_turtle_carbon`.

**Example**: you `/effort high`; the next request's API body has `output_config: {effort: 'high'}`; if the model is Opus 4.6, the thinking field is `{type: 'adaptive'}`; the status bar shows the grade in sync.

### 11.2 API login and authentication: four ways to pay, and a locked token refresh

**What it is**: one SIM can ride a contract plan, a prepaid card, or a company leased line — Claude Code supports four "ways to pay": a Claude subscription account (OAuth), an API key billed by usage, enterprise cloud channels (AWS Bedrock / Google Vertex / Azure Foundry).

**How the source implements it**:

- **Provider detection** (`utils/model/providers.ts:6-13`): look at env `CLAUDE_CODE_USE_BEDROCK`/`CLAUDE_CODE_USE_VERTEX`/`CLAUDE_CODE_USE_FOUNDRY`; if none are set, it is `firstParty`.
- **OAuth flow**: the `/login` deep dive of section 9.3 already covered it (PKCE + loopback listener, dual channel, `services/oauth/index.ts:14-120`).
- **Credential storage**: on macOS the OAuth token lives in the **Keychain** (`utils/secureStorage/index.ts:9-17`, falling back to a plaintext file on failure). API-key source priority (`utils/auth.ts`): `apiKeyHelper` (an executable configured in settings, `:123`) → `ANTHROPIC_API_KEY`/`ANTHROPIC_AUTH_TOKEN` env (`:126,134`) → `--settings` injection → keychain.
- **Locked token refresh** (`checkAndRefreshOAuthTokenIfNeeded`, `auth.ts:1427-1560`): on finding an expired `accessToken`, ① first clear the cache and re-read disk (**another process may already have refreshed**); ② if a real refresh is needed, grab a file lock (`lockfile.lock :1500`) against concurrent refresh by multiple processes; ③ retry up to 5 times (`:1453`); ④ a subscriber refresh omits scopes so the server can expand them (`:1544-1550`). On 401/"token revoked", the retry layer forces a refresh (`withRetry.ts:246-252`).
- **Billing header and cache-scope split**: the first block of every request's system prompt is `x-anthropic-billing-header: cc_version=...; cc_entrypoint=...; cch=...` (`constants/system.ts:86-95`). `splitSysPromptPrefix()` (`utils/api.ts:321-376`) lifts it out of the system prompt and marks `cacheScope: null` (does not participate in the prompt cache); remaining static blocks use `global`/`org` cache scopes — **changing entrypoint (CLI/SDK) does not break the cache**, a typical piece of careful accounting.

**Example**: after you `/login` and authorize, the token goes into the macOS Keychain; an hour later it expires; `checkAndRefreshOAuthTokenIfNeeded` takes the file lock and silently renews — two terminals both running Claude Code will not double-refresh; you notice nothing.

### 11.3 Model selection and fallback: lead actor and understudy

**What it is**: normally use the primary model; when Anthropic's servers are overloaded (HTTP 529), automatically swap in the backup so the conversation does not break.

**How the source implements it**:

- **Primary-model priority chain** (`getMainLoopModel` at `utils/model/model.ts:84-101`): ① what `/model` changed in-session (`bootstrap/state.ts:838`) → ② startup flag `--model` → ③ env `ANTHROPIC_MODEL` → ④ `model` in settings → ⑤ built-in default (Opus 4.6; third-party channels default Sonnet 4.5 because listing lags, the `@[MODEL LAUNCH]` comment at `:103-133`).
- **fallback specification**: `--fallback-model <model>` (`main.tsx:1000`, note the help text says "only works with --print"), and it is not allowed to be the same as the primary (`:1337` validation).
- **Trigger and switch**:

```mermaid
sequenceDiagram
    participant 循环 as queryLoop
    participant 重试层 as withRetry
    participant API as Anthropic API

    循环->>重试层: 请求（主模型 Opus）
    重试层->>API: 试第 1 次
    API-->>重试层: 529 超载
    重试层->>API: 试第 2 次
    API-->>重试层: 529
    重试层->>API: 试第 3 次
    API-->>重试层: 529（MAX_529_RETRIES=3，withRetry.ts:54）
    重试层-->>循环: 抛 FallbackTriggeredError（:163-165）
    循环->>循环: currentModel 换成 fallbackModel<br/>清空本轮半成品 assistant 消息<br/>（补齐 'Model fallback triggered' 工具结果）<br/><i>query.ts:894-910</i>
    循环->>重试层: 用 Sonnet 重发整个请求
    重试层->>API: 请求（fallback 模型）
    API-->>重试层: ✅ 正常回复
```

A detail: by default this only fires for "non-subscribers on non-custom Opus"; set `FALLBACK_FOR_ALL_PRIMARY_MODELS` to open it up (`:330-336`); background tasks (summaries, title generation) on 529 are dropped rather than retried with backoff (`:319-327`).
- **Small-model division of labor**: `getSmallFastModel()` (`claude.ts:339-340`) — conversation-topic detection, command descriptions, and other chores use a Haiku-class small model, separated from the main-loop model. Good steel on the cutting edge.

**Example**: `claude -p --model opus --fallback-model sonnet "重构这个模块"`. At peak, Opus returns 529 three times in a row; the loop silently switches to Sonnet and continues; you only see a model-switch notice in the transcript.

### 11.4 Six permission-mode grades: how long a leash the AI gets

**What it is**: from "ask me every step" to "fully off-leash," several grades, cycled with Shift+Tab.

**Semantics of the six grades** (`utils/permissions/PermissionMode.ts:42-91`):

| Mode | Semantics |
|---|---|
| `default` | ask via dialog on every dangerous operation |
| `plan` | research and propose only; must not touch files / run commands; at the end, use ExitPlanMode to hand the plan to you for approval (write operations intercepted from `utils/permissions/filesystem.ts:1446`) |
| `acceptEdits` | file edits inside the working directory are auto-allowed (`filesystem.ts:1366`); everything else still asks |
| `bypassPermissions` | skip all asking (dangerous; needs `--dangerously-skip-permissions` or a UI confirmation to enable) |
| `dontAsk` | not exposed in the UI cycle (`getNextPermissionMode.ts:70-72`) |
| `auto` | internal (ant-only) mode: a transcript classifier judges in real time whether each operation is safe (feature flag `TRANSCRIPT_CLASSIFIER`, `PermissionMode.ts:80-90`) |

**Shift+Tab cycle** (keybinding `keybindings/defaultBindings.ts:30-36`; on Windows without VT mode, falls back to `meta+m`):

```mermaid
flowchart LR
    D["default"] --> A["acceptEdits"] --> P["plan"] --> B["bypassPermissions<br/>（若可用）"] --> D
    style P fill:#bde0fe
    style A fill:#caffbf
    style B fill:#ffc6ff
```

Order is defined in `getNextPermissionMode.ts:34-79`; on switch, `cyclePermissionMode()` (`:88-101`) calls `transitionPermissionMode` to clean context (e.g. strip dangerous permissions when entering auto); the status bar changes color by mode (plan blue, accept cyan, bypass red, `PermissionMode.ts:26-30`).

**Example**: you want Claude to research a bug but fear it will thrash the code: Shift+Tab to `plan`; Claude reads code, writes a plan, and finally ExitPlanMode pops "approve the plan?"; after approval it auto-switches to `acceptEdits` and lands the edits (`filesystem.ts:1450` mode suggestion).

### 11.5 Output styles: a "personality mask" for Claude

**What it is**: an output style directly rewrites the opening of the system prompt so the same model answers in a different style — didactic vs. minimal, like changing a personality mask.

**How the source implements it**:

- **Built-in styles** (`constants/outputStyles.ts:42-100`): `default` (value null, no extra seasoning), `Explanatory` (explain every implementation choice, with a `★ Insight` block template), `Learning` (deliberately leave 2–10 lines as a small task for you to write, the `TODO(human)` mechanism).
- **Custom styles**: drop a markdown file into `~/.claude/output-styles/*.md` or the project's `.claude/output-styles/*.md`; frontmatter supports `name`, `description`, `keep-coding-instructions` (`loadOutputStylesDir.ts:41-62`); plugins can also provide them via an `output-styles/` directory.
- **Injection location**: the selected style is concatenated as a `# Output Style: {name}\n{prompt}` paragraph into the system prompt (`constants/prompts.ts:152-157`); the thesis sentence becomes "according to your Output Style below" (`:180`). `keep-coding-instructions: true` means keep the original coding instructions and only overlay style.

**Example**: after `/output-style Learning`, when Claude writes 30 lines of code it deliberately leaves the core function and sends a "Learn by Doing" card asking you to fill in the `TODO(human)` part — learn while you use.

### 11.6 The memory system: four "brains"

**What it is**: long-term memory across sessions (MEMORY.md auto-memory), the project covenant (the CLAUDE.md family), notes for the current session (Session Memory), a manual editor (`/memory`) — four layers.

**How the source implements it**:

- **Auto memory (memdir)**: `memdir/memdir.ts` — one memory directory per project (default `~/.claude/projects/<project-path>/memory/`); entry file `MEMORY.md` hard-capped at **200 lines / 25KB**, truncated with a warning when over (`memdir.ts:35,37-40,59-95`). Claude autonomously writes user preferences and project habits into markdown files under that directory; at startup they are retrieved by relevance and injected (`findRelevantMemories.ts`); injected entries carry a "freshness" prefix (`memoryFileFreshnessPrefix` at `FileReadTool.ts:749`). When you correct Claude, the tail of the denial message also appends a hint to "consider storing this in memory" (`utils/messages.ts:177-191`).
- **Session Memory**: `services/SessionMemory/sessionMemory.ts:1-7` — a background fork of a sub-agent periodically distills conversation points into notes, without interrupting the main dialogue; used to restore context after auto-compact.
- **CLAUDE.md system**: chapter 5 already covered it in detail (collection order, `@include`, identity tags); a subdirectory's CLAUDE.md is injected on demand when a file in it is read (`attachments.ts:1710` + `FileReadTool.ts:848,870`).
- **The `#` quick-memory shortcut no longer exists**: **stated clearly** — this source snapshot has no `#`-prefix input mode: `inputModes.ts:31-33` only recognizes `!` (bash mode), and the `PromptInputMode` type (`types/textInputTypes.ts:265-269`) only has bash/prompt etc. The early-version `#` quick-append memory has evolved into the `/memory` command + auto-memory system — the `#` usage in old tutorials on the web will not work in this version; note the version difference.

**Example**: you say offhand "our team writes commits in Chinese"; Claude writes that into `memory/team-conventions.md` and updates the `MEMORY.md` index; next week's new session, that preference shows up automatically in the system prompt with the relevant memories.

### 11.7 A quick look at other mechanisms

| Mechanism | One sentence | Key implementation |
|---|---|---|
| **Vim mode** | the input box supports NORMAL/INSERT switch, motion, operator, text object | pure-function modules `vim/motions.ts`, `operators.ts`, `textObjects.ts`, `transitions.ts`; state types `textInputTypes.ts:255-260` |
| **Voice input** | push-to-talk: connect to Anthropic's voice WebSocket for streaming transcription; **must have an OAuth token** (uses the claude.ai endpoint; API key/Bedrock/Vertex unavailable) | `services/voiceStreamSTT.ts:1-36`; gate `voice/voiceModeEnabled.ts:18-24` (GrowthBook kill switch) + `:31-40 hasVoiceAuth` |
| **Remote / bridge** | this machine becomes the "executor" for the claude.ai web/App: poll the cloud for tasks, run them locally, post results back; permission dialogs can be synced to the web for confirmation | `bridge/bridgeMain.ts`; `remote/SessionsWebSocket.ts`, `remotePermissionBridge.ts` |
| **cost tracking** | each API response's usage is converted to dollars per the model price list and accumulated; `/cost` displays; persisted across sessions | `cost-tracker.ts:3-27,146`; `utils/modelCost.js` |
| **telemetry** | events uniformly go through `logEvent()`, names of the form `tengu_*`; sensitive fields forced through a type tag (`..._I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS`) | `services/analytics/` (Statsig/GrowthBook/Datadog/first-party reporting) |
| **Keybinding system** | declarative bindings: the default table is chunked by context; users can override in `keybindings.json`; `ctrl+c`/`ctrl+d` are reserved and cannot be rebound | `keybindings/defaultBindings.ts:37+`, `loadUserBindings.ts`, `parser.ts`/`match.ts` |
| **UDS message service** | at startup, create a Unix Domain Socket under `$TMPDIR`; an external process can write messages to inject them into the conversation; the bind must finish before the SessionStart hook | `setup.ts:86-101` (feature `UDS_INBOX`, skipped by `--bare`) |
| **`--worktree` isolation** | the session runs inside a git worktree (`.claude/worktrees/<slug>`); changes do not touch the main workspace; the slug has path-traversal protection | `entrypoints/cli.tsx:247-261`; `utils/worktree.ts:52-55` |
| **SDK / headless** | `claude -p` non-interactive mode: no React tree; supports `--output-format json/stream-json`; QueryEngine abstracts "one query" into an engine shared by SDK and CLI | `cli/print.ts` (5594 lines, comment `:517`); `QueryEngine.ts:370,518` |
| **`--bare`** | slim startup: skip skill discovery, UDS, telemetry init, etc.; what you want is speed | `loadSkillsDir.ts:654-675`, `setup.ts:86-88` |

> 🔍 **Source pointers**: `src/utils/effort.ts:14-20,152-168` (effort) ｜ `src/utils/auth.ts:1427-1560` (token refresh) ｜ `src/utils/api.ts:321-376` (billing-header split) ｜ `src/utils/model/model.ts:84-101`, `src/services/api/withRetry.ts:54,163-165`, `src/query.ts:894-910` (model and fallback) ｜ `src/utils/permissions/PermissionMode.ts:42-91`, `getNextPermissionMode.ts:34-79` (permission modes) ｜ `src/constants/outputStyles.ts:42-100` (output styles) ｜ `src/memdir/memdir.ts:35-95`, `src/services/SessionMemory/sessionMemory.ts` (memory)

---

## 12. Memory and forgetting: context compaction

### 12.1 Why compact

As emphasized again and again: **every turn's request carries the entire history.** And the model's "brain capacity" (context window) is finite — say 200,000 tokens. A large refactor fills the conversation with hundreds of files and endless command output, and you hit the ceiling fast. Even if you do not hit it, longer history means slower responses, higher cost, and the model is more easily distracted by irrelevant old information.

### 12.2 Five layers of defense: a "slim-down check" at the start of every loop turn

At the **start of every loop turn** Claude Code runs five slim-down programs in sequence (`src/query.ts:379-648`):

```mermaid
flowchart LR
    A["每圈循环开头"] --> B["① applyToolResultBudget<br>超大工具结果落盘存文件<br>历史里只留'摘要+路径'"]
    B --> C["② snip<br>剪掉冗余片段"]
    C --> D["③ microcompact<br>按 tool_use_id 清理<br>已完成使命的旧工具结果"]
    D --> E["④ context collapse"]
    E --> F{"⑤ 快到触发线了吗？"}
    F -->|没到| G["✅ 正常请求模型"]
    F -->|到了| H["autocompact 自动压缩"]
    H --> G
```

The first four are "local weight loss" (no information loss, or very little); the fifth, **autocompact**, is the "major surgery."

### 12.3 The math of the trigger line: a 200k window, action around 190k

The trigger-line calculation (`src/services/compact/autoCompact.ts:62-76`) is simple arithmetic, but every number is deliberate:

```ts
export const AUTOCOMPACT_BUFFER_TOKENS = 13_000    // 警戒线余量
// ……
const MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3      // 熔断器
// 触发线 = effectiveContextWindow - AUTOCOMPACT_BUFFER_TOKENS
```

- **Effective window = model window − min(max output, 20000)**: reserve space for the model's "answer"; a 200k window is actually about 180k usable;
- **Trigger line = effective window − 13000**: 180k − 13k ≈ **action around 167k** (under a 200k window and default output config, the felt sense is that it fires before 190k tokens). Why leave a 13000 buffer? Because the compact summary itself takes space, and "compact early" is much calmer than "hit the wall then rescue";
- **Circuit breaker = give up after 3 consecutive failures**: the source comments record a bloody lesson — there used to be 1279 sessions that failed 50+ times in a row (max 3272), wasting about 250,000 API calls a day globally, so a breaker was added. Compaction failure burns money; retries cannot be allowed to run away;
- Env `DISABLE_AUTO_COMPACT` can turn the whole thing off (then hitting the "window − 3000" blocking limit errors directly, leaving room for you to `/compact` by hand).

### 12.4 How compaction is done: dispatch a "one-shot clone" to write reading notes

The most interesting part is how compaction is done: **not a crude chop of the first half, but dispatch a one-shot clone agent to read the whole conversation and write a summary** (`compactConversation` at `src/services/compact/compact.ts:387`; internally `runForkedAgent` reuses the same query loop, `querySource: 'compact'`, `maxTurns: 1`, one turn only).

The clone deliberately keeps **the same system prompt, the same tools, the same model, the same message prefix** — why? To hit the prompt cache: an identical prefix means most of this "write a summary" request's input can take the cache, saving money and time.

After the summary is written, `buildPostCompactMessages` (`compact.ts:330`) reassembles history:

```mermaid
flowchart TB
    subgraph BEFORE["压缩前的历史（约 19 万 token）"]
        O1["你的问题 1"] --> O2["AI 回复 + 几十个工具结果"] --> O3["你的问题 2"] --> O4["更多工具结果……"]
    end
    subgraph AFTER["压缩后的历史（可能只剩 2 万）"]
        S["📝 摘要消息：分身写的'读书笔记'<br>（之前对话的要点总结）"]
        BND["✂️ compact_boundary 压缩边界标记"]
        R["边界之后的少量原始对话"]
        S --> BND --> R
    end
    BEFORE ==>|compactConversation| AFTER
```

From then on `getMessagesAfterCompactBoundary` only takes content **after** the boundary plus that summary — like condensing a textbook into one page of notes before an exam, then reading only the notes and the last few chapters. Under a 200k window, one compaction usually takes 190k tokens down to twenty or thirty thousand, so the conversation can continue indefinitely (the cost is that detail before the boundary is gone, leaving only the points in the summary).

### 12.5 Reactive rescue: a 413 salvage after hitting the wall

If prevention failed and the API actually returned "content too long" (413), there is still **reactive compaction (`reactiveCompact`)**: the software quietly **hides** the error (so you do not see a scary message; `hasAttemptedReactiveCompact` in State guarantees only one try), compact once in an emergency, and resend. Only when every means has failed does it tell you "the conversation is too long; please `/compact` or start a new conversation" (`prompt_too_long` termination).

```mermaid
flowchart TD
    A["请求 API"] -->|413 prompt too long| B["withheld：错误先不显示"]
    B --> C["reactiveCompact 紧急压缩一次"]
    C --> D{"重试"}
    D -->|成功| E["✅ 用户无感知，循环继续"]
    D -->|失败| F["❌ 报 prompt_too_long<br>建议手动 /compact 或开新会话"]
    A -->|正常| G["继续主循环"]
```

> 🔍 **Source pointers**: `src/query.ts:379-648` (five layers of defense) ｜ `src/services/compact/autoCompact.ts:62-76,262,343` (trigger line and breaker) ｜ `src/services/compact/compact.ts:330,387` (boundary reassembly and clone summary)

---

## 13. Clones and the emergency brake: sub-agents and interrupt

### 13.1 Sub-agents: one engine, several run modes

When a task is large, the model can call the `Agent` tool to dispatch a "clone" onto a subtask (e.g. "go search the whole codebase for every use of this function"). Architecturally there is an elegant point (`src/tools/AgentTool/runAgent.ts:248`): **the sub-agent runs the main loop itself** — the same `query()` function (`runAgent.ts:748`), just with three things swapped: its own system prompt, a shrunk toolbox, and an identity tag (`agentId` is set, `querySource: 'agent:...'`). The main agent, the sub-agent, and the clone that writes summaries are all reuse of the same engine — which is also why the compact clone can "incidentally" hit the prompt cache.

The task system (`src/Task.ts`) defines seven task types: `local_bash` / `local_agent` / `remote_agent` / `in_process_teammate` / `local_workflow` / `monitor_mcp` / `dream`, with ID prefixes `b/a/r/t/w/m/d` respectively — the prefix of a task number you see in the UI already exposes its type.

### 13.2 Foreground vs background: two timings

A sub-agent has two run modes; the whole difference is "does the main loop wait":

```mermaid
sequenceDiagram
    participant 主 as 主循环
    participant 子 as 子 Agent

    Note over 主,子: 前台（同步）：主循环阻塞等待
    主->>子: 派任务（runAgent 同步调用）
    主-->主: ⏸️ 原地等待……
    子->>子: 自己跑完整的主循环（可能转很多圈）
    子-->>主: 最终结果作为 tool_result 返回
    主->>主: ▶️ 继续下一圈

    Note over 主,子: 后台（异步）：主循环立刻继续
    主->>子: 派任务（LocalAgentTask）
    子-->>主: 立刻返回 async_launched
    主->>主: ▶️ 继续干别的（可能又转了好几圈）
    子->>子: 后台独立运行
    子-->>主: 完成后往 messageQueueManager<br>塞一条 task-notification
    主->>主: 下一圈读到通知，继续决策
```

Imagine asking an assistant to look something up: foreground is you sitting there until they come back; background is you continuing your own work, and they WeChat you when they are done. **When to use which?** Use foreground when the next step depends on the subtask's result (you cannot walk without waiting); use background when you just want some "on the side" work (run tests in the background, monitor).

There is also an experimental **coordinator mode** (`feature('COORDINATOR_MODE')`): the main agent is demoted to a "coordinator" that can only use the three tools Agent / SendMessage / TaskStop — it does not work itself, only dispatches work and collects results, like a project manager.

### 13.3 ESC: an emergency brake that goes all the way to the network cable

You can press ESC at any time to interrupt the model. The full chain of this "brake line" is:

```mermaid
flowchart LR
    A["⌨️ 你按 ESC"] --> B["defaultBindings.ts:66<br>escape → chat:cancel"]
    B --> C["useCancelRequest.ts:87<br>handleCancel"]
    C --> D["abortController.abort()"]
    D --> E["signal 一路传到<br>claude.ts 的 HTTP 层"]
    E --> F["🔌 TCP 连接真正掐断<br>立刻停止计费"]
    F --> G{"循环在哪个检查点发现？"}
    G -->|流式接收中（:1015）| H["补孤儿 tool_result<br>return aborted_streaming"]
    G -->|工具执行中（:1485）| I["补孤儿 tool_result<br>return aborted_tools"]
```

This emergency brake is thorough:

1. **The HTTP request is actually cut** — it is not "pretend we did not see the result"; the connection is truly broken, billing stops immediately (the `signal` passed when issuing the request in section 6.4 is this brake line);
2. **Two checkpoints wind down cleanly**: cut mid-stream receive, or cut mid-tool execution; the loop first does the "patch orphan `tool_result`" of section 6.6 (otherwise the conversation is wrecked), then exits as `aborted_*`;
3. **ESC while idle has another use**: it pops the next message out of the message queue.

### 13.4 Layers of cancel: why ESC cannot kill a background task

Cancel signals propagate by "parent-child layer" (`createChildAbortController`, parent → child, one way):

```mermaid
stateDiagram-v2
    [*] --> 主循环Controller
    主循环Controller --> 前台子Agent: 共享同一个 controller
    主循环Controller --> 后台子Agent: 独立 controller
    主循环Controller --> 工具执行: 子 controller

    note right of 前台子Agent
        ESC → 父断子断，一起停
    end note
    note right of 后台子Agent
        ESC 停不了它（源码注释明说：
        background agents should survive
        when the user presses ESC）
        想杀它用 Ctrl+X Ctrl+K
    end note
```

Why do background tasks deliberately **not** die with ESC? Because it would be counter-intuitive: you press ESC to stop "the work currently painting the screen," not to kill a background test you dispatched ten minutes ago that has already been running for a while — the cost of a mistaken kill is far greater than the bother of "one extra dedicated shortcut."

> 🔍 **Source pointers**: `src/tools/AgentTool/runAgent.ts:248,748` (sub-agent reuses query) ｜ `src/Task.ts` (seven task types) ｜ `src/keybindings/defaultBindings.ts:66` (ESC binding) ｜ `src/hooks/useCancelRequest.ts:87` (`handleCancel`) ｜ `src/query.ts:1015,1485` (two interrupt checkpoints)

---

## 14. The full picture: one diagram of the whole path

String every previous chapter together and this is the complete journey after you send a prompt:

```mermaid
flowchart TD
    subgraph 你的电脑["💻 你的电脑（Claude Code 软件）"]
        A["⌨️ 敲字回车<br><i>useTextInput → PromptInput → REPL</i>"] --> B["分流判断（纯字符串规则）：<br>空? exit? !shell? /命令?"]
        B -->|本地命令| C["⚙️ 本地执行<br><b>不发 AI</b>"]
        B -->|普通提问| D["📦 打包：系统提示词(+gitStatus)<br>+ &lt;system-reminder&gt;(CLAUDE.md+日期)<br>+ 全部历史 + 工具清单"]
        D --> E{"🔁 queryLoop while(true)"}
        E --> F["压缩防线五连<br>（落盘/snip/microcompact/collapse/autocompact）"]
        F --> G["流式请求模型<br><i>裸 stream，SSE 大 switch</i>"]
        G --> H{"有 tool_use？"}
        H -->|有| I["权限五关裁决<br>deny→ask→自查→模式→alwaysAllow→弹窗"]
        I -->|被拒| J["拒绝原因回喂模型<br>（模型自我纠正）"]
        I -->|放行| K["🔧 执行工具（边收边跑<br>只读并行/写操作串行）"]
        K --> L["tool_result 追加进历史"]
        J --> L
        L --> E
        H -->|没有| M["🎉 return completed<br>显示最终答案"]
        N["ESC 急刹车<br><i>signal 直达 HTTP 层</i>"] -.随时.-> G
        N -.随时.-> K
    end
    G -.->|HTTPS stream:true| O["☁️ Anthropic 服务器<br>Claude 大模型"]
    O -.->|SSE 事件流| G
    P["🤖 子 agent / 压缩分身<br>（复用同一个 query()）"] -.被 Agent 工具唤起.-> E
```

---

## Appendix: source map

If you want to read the source yourself, here is a map of the files this lecture cites:

| Topic | File | What to look at |
|---|---|---|
| Program entry | `src/entrypoints/cli.tsx` | the "sorter" `main()`, `--version` with zero loading |
| Global init | `src/entrypoints/init.ts` | memoized init, preconnect API |
| Session init | `src/setup.ts` | working directory, hooks snapshot |
| UI render | `src/ink/`, `src/components/`, `src/screens/REPL.tsx` | terminal UI, input box |
| Key handling | `src/hooks/useTextInput.ts` | Enter's four fates (`:247`) |
| Input routing | `src/utils/handlePromptSubmit.ts`, `src/utils/processUserInput/` | blank drop (`:188`), exit list (`:196`), `shouldQuery` |
| Slash commands | `src/commands.ts` | `findCommand` exact match (`:688`) |
| Context assembly | `src/constants/prompts.ts`, `src/context.ts`, `src/utils/api.ts` | `getSystemPrompt` (`:444`), placement (`:437/:449`) |
| Memory files | `src/utils/claudemd.ts` | collection order (`:790`), `@include` (`:618`), identity tags (`:1153`) |
| **Main loop (the heart)** | **`src/query.ts`** | `queryLoop` (`:241`), `while(true)` (`:307`), patch orphans (`:123`) |
| API transport | `src/services/api/claude.ts` | raw stream (`:1822`), SSE switch (`:1980-2303`) |
| Tool definitions | `src/Tool.ts`, `src/tools.ts` | `buildTool` (`:783`), `getAllBaseTools` (`:193`) |
| Tool execution | `src/services/tools/toolExecution.ts`, `StreamingToolExecutor.ts` | `runToolUse` (`:337`), concurrency discipline |
| Permission system | `src/utils/permissions/permissions.ts`, `dangerousPatterns.ts` | five-gate adjudication (`:1158`) |
| Hook system | `src/utils/hooks.ts`, `src/services/tools/toolHooks.ts` | spawn execution, PreToolUse (`:435`) |
| Context compaction | `src/services/compact/` | trigger line (`autoCompact.ts:62`), clone summary (`compact.ts:387`) |
| Sub-agents | `src/tools/AgentTool/runAgent.ts`, `src/Task.ts` | reuse `query()` (`:748`), seven task types |
| Interrupt | `src/hooks/useCancelRequest.ts`, `src/keybindings/defaultBindings.ts` | ESC chain (`:87/:66`) |
| Tool field guide (chapter 8) | `src/tools/` (39 tool directories) | Bash compound-command split (`BashTool.tsx:447-465`), read-before-write (`FileEditTool.ts:281`), three-flag defaults (`Tool.ts:750-761`) |
| Tool delayed load | `src/tools/ToolSearchTool/`, `src/utils/toolSearch.ts` | defer list (`prompt.ts:62-108`), search scoring (`ToolSearchTool.ts:132-216`) |
| MCP tool wrapping | `src/services/mcp/client.ts` | `fetchToolsForClient` (`:1743-1832`), annotations→three flags (`:1795-1809`) |
| Slash commands (chapter 9) | `src/commands.ts`, `src/commands/` | `COMMANDS` registration (`:258`), merge order (`:447-470`), safety lists (`:619/:651`) |
| Command dispatch | `src/utils/processUserInput/processSlashCommand.tsx` | `getMessagesForSlashCommand` (`:525`), fork execution (`:62`), prompt-command lifecycle (`:827`) |
| Representative commands | `src/commands/conversation.ts`, `commands/commit.ts`, `commands/init.ts` | `/clear` soft restart (`:49`), pre-execute + whitelist (`:16-19`), `/init` script (`:226`) |
| Custom command / skill load | `src/skills/loadSkillsDir.ts`, `src/utils/promptShellExecution.ts` | frontmatter (`:185-265`), namespace (`:523-552`), shell pre-execute red line (`:371-374`) |
| Immediate commands | `src/screens/REPL.tsx`, `src/utils/immediateCommand.ts` | bypass the queue (`:3158-3184`) |
| Plugin ecosystem (chapter 10) | `src/utils/plugins/pluginLoader.ts`, `marketplaceManager.ts` | directory-structure probe (`:1392-1398`), sparse clone (`:803/:1034`) |
| Skill progressive disclosure | `src/utils/attachments.ts`, `src/skills/bundledSkills.ts` | listing attachment (`:2661`), bundled-skill registration (`:53`) |
| MCP ecosystem | `src/services/mcpServerApproval.tsx`, `services/mcp/types.ts` | startup approval (`:13-40`), connection state machine (`:183-248`) |
| effort mechanism (chapter 11) | `src/utils/effort.ts`, `src/utils/thinking.ts` | priority chain (`:152-168`), adaptive vs budget (`:10-13`) |
| Auth and token | `src/services/oauth/index.ts`, `src/utils/auth.ts`, `src/utils/secureStorage/` | PKCE (`:14-120`), locked refresh (`:1427-1560`), Keychain |
| Model and fallback | `src/utils/model/model.ts`, `src/services/api/withRetry.ts` | priority chain (`:84-101`), `FallbackTriggeredError` (`:163-165`) |
| Permission modes | `src/utils/permissions/PermissionMode.ts`, `getNextPermissionMode.ts` | six-grade semantics (`:42-91`), Shift+Tab cycle (`:34-79`) |
| Output styles | `src/constants/outputStyles.ts`, `outputStyles/loadOutputStylesDir.ts` | built-in styles (`:42-100`), injection location (`prompts.ts:152-157`) |
| Memory system | `src/memdir/memdir.ts`, `src/services/SessionMemory/sessionMemory.ts` | `MEMORY.md` cap (`:35-95`), session notes |
| Feature quick look | `src/vim/`, `src/voice/`, `src/bridge/`, `src/cost-tracker.ts`, `src/cli/print.ts` | entries for vim/voice/remote/cost/SDK |

> **A note about this source tree**: this is a TypeScript snapshot of an internal build (restored via sourcemap). Function names are mostly preserved; comments are thorough. You may see compile-time switches like `feature('XXX')` and a few missing files (e.g. some bash-permission modules are merged in this snapshot); those are normal traces of the build system and do not block understanding the main flow.

---

## Takeaway: three sentences to leave with

1. **The model can only talk; the agent software is its hands and eyes** — Claude Code's entire job is "translate the model's intent into operations, translate the results of those operations back into text."
2. **The heart is a `while` loop**: request the model → if there is a `tool_use`, execute it and append the result → request again → until the model no longer wants a tool, and the answer is out. Compaction, streaming, sub-agents, interrupt — all of them are supporting facilities around this loop.
3. **Safety is architecture, not virtue**: the dead rules of input routing, the five-gate permission adjudication, the hooks snapshot, the ESC brake that goes all the way to the network cable — these are hard constraints at the software layer. Whatever the model wants to do, it has to "apply for approval," and the approval sits with you.

Once you have those three, look at any AI agent product on the market (Cursor, Devin, various automation assistants) and you will find they do not escape this basic frame — the differences are how rich the toolbox is, how fine-grained the loop is, and how strict the safety mechanisms are.

### After you finish: where to practice next

After the first eleven chapters (the mechanism spine + the four field guides of tools / commands / ecosystem / features), the best consolidation is to use your hands. Easiest to hardest:

1. **Write your own slash command** (section 9.4): drop a `.md` into `.claude/commands/`, use `$ARGUMENTS` and `` !`shell` `` pre-execution; in ten minutes you have your first "private shortcut";
2. **Upgrade it to a skill** (section 10.2): move it into `.claude/skills/<name>/SKILL.md`, write `when_to_use` and `allowed-tools`, and feel how "progressive disclosure" lets the model find it itself;
3. **Install an MCP server** (section 10.3): register one in the project's `.mcp.json` (e.g. the official GitHub server), walk the startup approval, then look at the `mcp__*` tools that appeared in the tool list — you will feel "USB-C" plug-and-play for real;
4. **Walk the official plugin marketplace** (section 10.1): `/plugin marketplace add anthropics/claude-plugins-official`, install a plugin, then go back and read its `plugin.json` and directory structure, and see how other people package a workflow;
5. **Run `/init` once and review the product line by line** (section 9.3): have it generate a CLAUDE.md for your project, then edit it yourself against the standard "if we deleted this line, would Claude make a mistake?" — the fastest way to put chapters 5 and 11 to work.

Do these five things and you have not only "read" how Claude Code works; you are using it in the philosophy it was designed with.


