Skip to content

第 12 章 Token 控本与故障恢复

控本靠两件事——精确上下文(不喂无关文件)+ 选对模型与档位;故障恢复靠两件事——checkpoint 纪律 + 对抗幻觉的纪律。这两组纪律其实是同一个问题的两面:都是上下文管理。上下文管得住,token 自然降,幻觉自然少,失败也能回退;上下文管不住,钱花得快,bug 也藏得深。

12.1 结论:控本与故障恢复同源

很多人把"省 token"和"防故障"当成两件事,分别优化。这是错的。

Claude Code 的 token 消耗,本质上是无关状态占据上下文:过大的 CLAUDE.md、一次性塞进来的整个仓库、长日志、lockfile、已排除的假设、旧测试输出。这些噪音不仅贵,还互相干扰,让模型在第 40 轮之后开始遗忘你第 3 轮说的约束。

故障的根因也一样:上下文污染 + 幻觉累积。Claude 凭记忆而非凭文件说话、交叉验证缺失、checkpoint 缺失导致无法回退——每一次幻觉都在为下一次幻觉铺路。

所以本章不讲"省钱技巧",而是讲一条贯穿始终的纪律:管好上下文,钱和可靠性一起到手。这条纪律有四个抓手:

抓手控本作用故障恢复作用
精确投喂不花冤枉钱减少幻觉输入
模型/档位选型把简单任务交给便宜档复杂任务用强模型降失败率
checkpoint失败可回退
对抗幻觉不让错误累积成事实

下面分四块展开,最后给出常见报错与失败边界。

12.2 用 /cost 与 /context 量化消耗

命令

text
/cost

查看当前会话累计 token 消耗与折算费用。

text
/context

查看上下文占用分布——哪些文件、哪些工具输出、哪些 skill 占了大头。

预期输出(示例,非真实转录)

text
> /cost
Total cost:           $0.42
Total duration:       18m 32s
Total input tokens:   312,540
Total output tokens:   28,901
Cache read tokens:    245,120
Cache write tokens:    67,420

> /context
Context left: 68% (≈136,000 tokens)
Top contributors:
  1. CLAUDE.md (project)           8,200
  2. src/api/routes/session.ts     6,400
  3. Test output (npm test)       12,800
  4. Earlier exploration          34,000
  5. skills/claude-api.md          3,100

验证

  • /cost 输出的总费用与你的计费面板或订阅用量趋势一致(订阅用户看不到单价,但能看到 token 量级)。
  • /context 中"Top contributors"排名前几项,应当都是当前任务确实需要的文件。如果出现 dist/coverage/node_modules/*.lock、大型 JSON 响应,就是控本机会。

失败边界

  • 不要把 /cost 当仪表盘每五分钟看一次——它本身不省钱,反而打断节奏。只在会话变长、任务切换、或模型开始遗忘约束时看。
  • /context 显示的是"当前快照",不是"历史峰值"。如果你刚跑过一个 10 万 token 的测试输出然后被自动压缩了,/context 看不到它,但 /cost 记得。
  • 订阅用户(Pro/Max)看到的是 token 量,不是美元;不要拿 API 单价去反推"我这会话花了多少钱",订阅是打包计费,逻辑不同(详见第 32 章)。

12.3 精确上下文:路径投喂四原则

原则一:用 @文件 精确投喂,不要 @. 全塞

差的 Prompt:

text
检查这个项目的登录问题 @.

@. 会把整个工作目录的文件信息塞进上下文,Claude 的反应不是"更聪明",而是"更迷茫":它要在几百个文件里找线索,token 花在无关扫描上,幻觉也从这些噪音里长出来。

更好的 Prompt:

text
调查登录后刷新页面丢失会话的问题。

入口文件:
- @apps/web/src/auth/session.ts
- @apps/web/src/auth/AuthProvider.tsx
- @apps/api/src/routes/session.ts

先搜索 refreshSession 的直接调用者和相关测试。
不要读取 dist、coverage、node_modules、fixtures/snapshots。
只修改 auth 相关文件。

@file 把文件内容直接带入上下文;@directory 主要提供目录树信息。前者精度高,后者范围广。优先用前者。

原则二:先画最小依赖图,再深入

大型仓库里,不要说"读完再说"。用两阶段探索:

text
阶段 1,只搜索不修改:
- 找到 refreshSession 的定义和直接调用者
- 找到相关测试
- 找到共享 token contract
- 最多读取 8 个文件
输出候选文件清单和选择理由,不要直接改代码。

确认清单后:

text
阶段 2,只读取你列出的 5 个核心文件。
给出失败路径和最小修复方案,不要扩展到其他模块。

给探索设文件预算不是为了机械限制,而是防止 Claude 把"理解代码库"误解成"扫描全部代码"。

原则三:排除清单写进 CLAUDE.md

把"永远不要读"的目录写进项目 CLAUDE.md,一次声明长期生效:

markdown
## 上下文排除

不要读取以下目录,除非任务明确要求:
- dist/、build/、.next/、coverage/
- node_modules/、vendor/
- *.lock、*.min.js、*.map
- fixtures/snapshots/、__snapshots__/
- 大型生成文件(>1000 行的自动生成代码)

这比每次在 Prompt 里重复"不要读 dist"省力,也更可靠——CLAUDE.md 是稳定上下文,每次启动自动加载。

原则四:任务切换就清空

完成"支付 Bug 修复"后,不要在同一会话继续"设计营销落地页"。两个无关任务的上下文互相污染,既贵又容易让模型把上一个任务的约束带到新任务里。

验证

text
/context

前几项应当都是当前任务的核心文件。如果出现上一个任务的残留(旧 diff、旧测试输出、旧搜索结果),说明该 /clear/compact 了。

失败边界

  • @文件 不是越多越好。投喂 20 个文件让 Claude"自己挑",不如先让它搜出 5 个核心文件再投喂。
  • 排除清单不要写得太激进。如果 Claude 真的需要读 package.json 来确认依赖版本,你却把整个根目录排除了,它会瞎猜。
  • 两阶段探索的"文件预算"是软约束,Claude 可能略超。如果它读了 12 个文件而不是 8 个,不要恐慌,看它读得对不对;如果它读了 40 个,就是失控,按 12.7 的幻觉对抗流程处理。

12.4 模型与档位选型

模型选错,再省 token 也是浪费。用 Opus 跑格式化是杀鸡用牛刀,用 Haiku 做架构决策是让小学生写论文。

/model:四档模型

text
/model

进入选择器,或直接 /model sonnet 切换。2026-07-08 核对(v2.1.202+)的模型基线:

模型定位适用场景成本量级
Opus 4.8旗舰推理架构决策、复杂重构、跨文件一致性
Fable 5长程推理长上下文研究、多步骤规划中高
Sonnet 5均衡主力日常编码、Bug 修复、代码审查
Haiku 4.5轻量快速格式化、简单重命名、批量小任务

/effort:六档推理深度

text
/effort

档位:lowmediumhighxhighmaxultracode。档位越高,模型思考越深,token 消耗越大,但复杂问题的成功率也越高。

档位适用注意
low简单替换、格式化复杂逻辑会出错
medium日常编码默认档大多数任务用这个
high跨文件改动、Bug 排查略贵
xhigh/max架构设计、疑难 Bug仅复杂任务
ultracode极限推理(research preview)最贵,留给硬骨头

/fast:Fast Mode

text
/fast

切换 Fast Mode(基于 Opus 4.8/4.7):速度约 2.5x,成本约 1/3。适合迭代快、容错高的任务——原型探索、批量小改、跑验证。不适合最终交付——关键代码、安全审查仍用完整 Opus。

选型矩阵

任务类型模型档位Fast
格式化、重命名Haiku 4.5low
日常 Bug 修复Sonnet 5medium
代码审查(主审)Sonnet 5high
架构设计、Plan ModeOpus 4.8xhigh
疑难 Bug、跨模块重构Opus 4.8max/ultracode
长上下文研究、代码考古Fable 5high视情况

API vs 订阅:成本逻辑预告

  • 订阅用户(Pro/Max):固定月费,token 不按量计费,但有用量上限。控本的核心是别撞上限——会话越长、模型越强,越早耗尽配额。策略:简单任务自觉降档到 Sonnet/Haiku,省下配额给复杂任务。
  • API 用户:按 token 计费,控本就是省钱。/cost 看的是真金白银。策略:CI/脚本用 --max-budget-usd 设硬上限,日常用缓存(CLAUDE.md 稳定部分会命中 cache read,单价大幅降低)。

两者的成本逻辑差异很大,第 32 章会详细对比订阅 vs API 的盈亏平衡点与切换时机。

验证

  • 切换后用 /status/model 确认当前模型与档位已生效。
  • 跑一个已知任务,对比 token 消耗(/cost)与完成质量。如果 Sonnet medium 能搞定的任务用 Opus ultracode,token 量会差 5-10 倍。

失败边界

  • 不要默认全程用 Opus + ultracode。这是最贵的组合,也是最容易撞配额/超预算的组合。只在复杂任务时手动升档,完成后再降回来。
  • Fast Mode 不是"免费提速"。它在某些复杂推理上质量略降,关键代码、安全审查、最终交付不要用 Fast。
  • Haiku 4.5 不擅长多步推理。让它做"把这三个函数重命名"可以,让它做"分析这个并发 Bug"会翻车。
  • ultracode 是 research preview(2026-07-08 核对),行为可能随版本调整,不要把它写进不可变的生产流水线。

12.5 压缩与清空:任务边界即上下文边界

当会话变长,你有三个工具:/compact(压缩)、/clear(清空)、/rewind(回退)。用错场景,要么丢信息,要么白花钱。

/compact:同任务继续,带指令压缩

text
/compact 保留:
- 当前 Bug 的已确认根因
- 修改过的文件清单
- 尚未通过的测试及最新错误信息
- 用户明确约束和禁止事项
- 下一步验证命令

丢弃:
- 已排除的假设
- 旧测试输出
- 重复的文件摘要

为什么写聚焦指令:压缩不是无损归档。默认压缩会丢掉它认为不重要的东西,而它认为不重要的,可能恰恰是你下一步需要的。明确告诉它"留什么、丢什么",才能保住后续行动所必需的状态。

/clear:换任务,带标签清空

text
/clear payment-timeout-fixed

参数是给即将被清除的旧对话打标签,方便日后用 /resume 选择器找回。新会话仍会加载项目级 CLAUDE.md,但不会携带上一任务的临时讨论。

/rewind:回到 checkpoint

text
/rewind

进入交互式选择,回到此前某个 checkpoint:可只回退对话、只回退代码、二者都回退,或从某条消息开始把后续压缩成摘要。别名 /checkpoint/undo。这些都是菜单里的回退目标,不是命令行命名参数。

适用场景表

操作使用场景不适用场景
/compact同任务继续,历史变长换了完全不同的任务
/clear开始无关新任务同任务还有明确未完成步骤
/resume回到旧任务继续当前任务未结束
/rewindClaude 跑偏了,回到最近正确点想保留最近所有探索

验证

  • /compact 后用 /context 确认:核心文件、约束、失败测试仍在,旧噪音已清。
  • /clear 后用 /context 确认:只剩 CLAUDE.md 与项目级稳定上下文。
  • /rewind 后用 git diff 确认:代码状态确实回到了你期望的那个点。

失败边界

  • /compact 是有损的。如果你正在排查一个微妙的并发 Bug,压缩可能丢掉那个"看似无关但其实是线索"的细节。关键证据先写进 CLAUDE.md 或单独文件,再压缩。
  • /clear 不可逆(除非你打了标签且用 /resume 找回)。清空前确认当前任务真的结束了。
  • /rewind 只回退 Claude 编辑的代码,不会回退你在编辑器里手动改的内容。手动改的东西靠 git 管理,不靠 checkpoint。

12.6 checkpoint 纪律:Git 是最后防线

Claude Code 的 /rewind checkpoint 很方便,但它不是 Git 的替代品。真正的高风险修改,必须用 Git commit 做检查点。

高风险修改前的 checkpoint 习惯

bash
git status --short
git diff
git add -A && git commit -m "checkpoint: before refactor session module"

为什么不能只靠 /rewind:

  • /rewind 的 checkpoint 是 Claude 编辑前自动创建的,但你手动改的内容不在它的视野里
  • /rewind 的 checkpoint 存在会话本地,会话结束(/clear、关闭终端)后可能不可达。
  • Git commit 是全局的、可推送的、可审计的。团队协作时,只有 Git 是共同语言。

失败回退流程

Claude 跑偏时的标准停止顺序:

  1. Esc 中断当前动作。
  2. git diff 查看已发生变化。
  3. 明确收缩范围和禁止事项,重新下指令。
  4. 若已无法挽回,git checkout -- <file>git reset --hard HEAD 回到 checkpoint。

注意第 4 步:git reset --hard破坏性操作,会同时删除任务开始前的未提交改动。属于 CLAUDE.md 红线边界,即使有 auto-accept 也要二次确认。更安全的做法是:

bash
git stash push -m "wip: claude diverged" -- src/
git checkout -- src/

或直接让 Claude 只回退它本轮的改动:

text
只撤销你在本轮对 apps/api/src/routes/session.ts 的修改。
保留任务开始前已存在的用户改动。
先展示将恢复的 diff,不要执行破坏性 Git 命令。

验证

  • checkpoint commit 后,git log --oneline -1 确认提交存在且消息清晰。
  • 回退后,git status 确认工作树状态符合预期,没有残留改动。

失败边界

  • checkpoint 缺失是最大的失败。没有 checkpoint,Claude 跑偏后你只能手动一个个文件 git diff 找回,成本极高。
  • 不要让 Claude 自己说"我回退了"就信。用 git diff 看真实状态,Claude 的描述与磁盘真相可能不一致。
  • git reset --hard 永远是最后手段,且必须人工执行,不让 Claude 自动跑。

12.7 对抗幻觉的四道防线

幻觉是 LLM 的固有属性,无法消除,只能对抗。Claude Code 的幻觉有四类:凭记忆而非凭文件、交叉验证缺失、API 名记错、盲目信任自身输出。对应四道防线。

防线一:让 Claude 读文件确认,而非凭记忆

差的 Prompt:

text
session.ts 里那个刷新逻辑有什么问题?

这等于让 Claude 凭记忆描述它"记得"的代码。即使文件之前读过,也可能记错。

更好的 Prompt:

text
读 @apps/web/src/auth/session.ts,定位 refreshSession 函数,
指出它在 token 过期时的处理逻辑,引用具体行号。

强制它读文件、引用行号。如果它说"大概在第 X 行"而不读文件,打断它,要求重新读。

防线二:交叉验证关键事实

Claude 说"这个 API 已废弃"、"这个函数没有调用者"、"这个配置项不存在"时,不要直接信。让它用工具验证:

text
用 grep 搜索 refreshSession 的所有调用者,列出文件和行号。
用 git log 确认这个 API 最近一次变更的时间和原因。

交叉验证的纪律:凡是可以用工具验证的事实,就不接受凭记忆的陈述

防线三:CLAUDE.md 记录准确 API 名

Claude 会记错 API 名、库名、配置键。把项目中"容易记错但必须准确"的东西写进 CLAUDE.md:

markdown
## 关键 API 名(禁止用记忆,以本表为准)

- 会话刷新:refreshSession(不是 refresh_session、不是 refreshToken)
- 用户表:users(不是 user、不是 UserTable)
- 配置键:NEXT_PUBLIC_API_URL(不是 API_URL、不是 NEXT_PUBLIC_API)
- 数据库 ORM:Prisma with PrismaPg adapter(不是默认 prisma client)

CLAUDE.md 是稳定上下文,每次启动自动加载。把易错点写进去,Claude 在每次生成代码前都能看到正确版本。

防线四:代码审查是最终控制面

Claude 说"修复了"不算数,git diff 看到的才算数。最后一步永远是人工审查真实变更,辅以 /code-review/security-review(第 13 章)。

text
/code-review --effort high

让审查工具看真实 diff,而不是听 Claude 的描述。审查工具不会因为"我刚改完"就放水。

验证

  • Claude 引用代码时,抽查一两个引用:打开文件看那行是不是它说的样子。
  • Claude 声称"已废弃/不存在"时,让它给出验证命令的结果(grep、git log、文档 URL)。
  • CLAUDE.md 里的 API 名表,定期与代码实际用法对齐(grep -r "函数名" src/)。

失败边界

  • "读文件确认"不是让 Claude 把整个仓库读一遍。仍是 12.3 的精确投喂原则——读相关文件,不是读所有文件。
  • 交叉验证有成本。不是每个事实都要验证,只验证会影响决策的关键事实(API 是否废弃、函数是否有调用者、配置是否生效)。
  • CLAUDE.md 不是越长越好。把易错点写进去,不是把整个 API 文档抄进去。CLAUDE.md 本身也占上下文,过长会反噬控本目标。
  • /code-review 是辅助,不是替代。它可能漏掉业务逻辑错误,最终判断仍是人的责任。

12.8 输出截断:拆分与并行

长输出是 token 消耗的大头,也是截断风险最高的场景。两种策略:横向拆分(多步骤)、纵向并行(子代理)。

策略一:骨架→扩写,横向拆分

不要一次性让 Claude"写完整个模块"。拆成两步:

text
步骤 1:只输出模块骨架——函数签名、类型定义、注释说明每个函数做什么。
不要写实现体。输出后停下,等我确认。

步骤 2(确认后):逐个函数填充实现,每个函数写完跑一次类型检查。

好处:骨架阶段 token 少,错了便宜改;实现阶段有了正确骨架,出错率低。比一次写完再返工省 30-50% token。

策略二:子代理并行,纵向切分

大规模重构、多模块独立任务时,用子代理并行。每个子代理有独立上下文,互不干扰,主上下文只收结论。

text
用 3 个子代理并行:
- Agent A:重构 src/auth/,独立 worktree
- Agent B:重构 src/api/,独立 worktree
- Agent C:更新测试,独立 worktree
各自完成后回主上下文合并。

这是第四部分(第 19 章)的主题,这里只点明:子代理并行不仅快,而且每个代理的上下文更短、更聚焦,token 总量往往低于单代理长会话。因为长会话有累积噪音成本,短会话没有。

CI 或脚本中必须设上限:

bash
claude -p \
  --max-turns 5 \
  --max-budget-usd 2.00 \
  --output-format json \
  "审查当前 diff,只输出高置信度 correctness 和 security 问题"
  • --max-turns 防止 Agent 循环探索失控。
  • --max-budget-usd 为 API 计费设硬上限。
  • --output-format json 便于程序判断结果。
  • 只分析 stdin 时禁用工具:--tools ""

陷阱:--tools "" 只禁内置工具,不会禁用已连接的 MCP 工具。要彻底封锁 MCP,追加 --disallowedTools "mcp__*" 或用 --strict-mcp-config

验证

  • 拆分步骤后,每步用 /cost 对比单次长输出的消耗。骨架+实现通常比一次写完省。
  • 子代理并行后,主上下文 /context 应当只显示结论,不显示各子代理的探索过程。
  • Print mode 跑完后,检查退出码与 JSON 输出,确认未被 budget/turns 截断。

失败边界

  • 拆分不是越细越好。拆得太细,Claude 失去整体视角,各步骤之间不一致。骨架→实现是合理粒度,骨架→类型→签名→文档→实现就过了。
  • 子代理并行有协调成本。3 个独立任务适合,1 个耦合任务硬拆成 3 份会更慢更错。判断标准:任务之间能否独立验证
  • Print mode 的 --max-turns 5 对复杂审查可能不够。如果输出被截断,先看是不是任务太复杂,而不是直接加到 50。

12.9 常见报错与解决(2026)

以下报错与场景基于 2026-07-08 核对(v2.1.202+)的常见形态。具体错误信息可能随版本调整,以实际为准。

报错一:上下文溢出

现象:会话变长后,Claude 开始遗忘早期约束、重复读文件、或直接提示上下文不足。

根因:累积的探索过程、旧 diff、旧测试输出占据了窗口。

解决:

  1. /context 看占用分布。
  2. 能丢的用 /compact 带指令 压缩。
  3. 换了子任务就 /clear
  4. 超长任务拆成子代理(第 19 章)。

失败边界:不要在上下文已溢出时还硬撑。继续对话只会让幻觉更严重。先压缩或清空,再继续。

报错二:权限烦(频繁弹确认)

现象:每跑一个命令都弹权限确认,打断节奏。

根因:权限规则没配好,或用了过窄的 allow 模式。

解决:

  1. /fewer-permission-prompts 扫描历史,自动生成白名单(第 05 章)。
  2. 读操作全部 allow:Bash(git diff *)Bash(git status *)Bash(npm test *)Bash(npm run lint *)
  3. 写操作按项目约定放开,破坏性操作永远 deny。
  4. 长会话用 --permission-mode acceptEdits,但显式 deny 危险模式(第 05 章)。

失败边界:acceptEdits 不只自动批准文件编辑,还可能自动批准 mkdirtouchrmmvsed 等工作目录文件命令。即使开了 acceptEdits,也要 deny Bash(rm -rf *)Bash(git reset *)Bash(git push *) 等。

报错三:命令执行失败

现象:Claude 跑的命令报错,它开始反复猜测参数或路径。

根因:Claude 凭记忆猜命令,而非先读 package.json / Makefile 确认实际脚本名。

解决:

  1. 把项目实际命令写进 CLAUDE.md(npm testnpm run lint:fixmake build)。
  2. 让 Claude 失败时先读 package.json 的 scripts 段,再重试。
  3. 复杂命令用 --tools 限制,或封装成 MCP 工具/自定义 skill(第 16 章)。

失败边界:不要让 Claude 反复试同一个错命令。连续失败 2 次就打断,让它读配置文件确认,而不是继续猜。

报错四:Claude 幻觉(凭记忆说话)

现象:Claude 说"这个函数不存在"、"这个 API 已废弃"、"我已经修改了 X",但实际并非如此。

根因:LLM 的固有幻觉,长上下文下更严重。

解决:走 12.7 的四道防线:

  1. 强制读文件,引用行号。
  2. 关键事实用 grep/git log 交叉验证。
  3. 易错 API 名写进 CLAUDE.md。
  4. 最终用 /code-review 看真实 diff。

失败边界:不要因为 Claude 说得很自信就信。LLM 的自信程度与正确程度没有强相关。验证,验证,再验证。

12.10 失败边界汇总

本章的纪律,反向看就是失败边界。三条最致命的:

边界一:为省 token 砍掉验证步骤

最危险的"省 token"方式:跳过测试、跳过类型检查、跳过 code review。理由是"这次改动很小,不用跑验证"。

后果:小改动藏着大 bug,等你发现时已经污染了后续所有工作。返工成本是验证成本的 10 倍。

正确做法:验证命令是硬约束,不可压缩。要省 token,省在探索阶段(精确投喂、文件预算),不省在验证阶段。CLAUDE.md 里的"验证"段必须每条跑完。

边界二:盲目信任输出

Claude 说"修复了"、"测试通过了"、"没有副作用"——这些陈述在没有 git diff 与验证命令背书时,都是待验证的假设,不是事实。

后果:错误累积成事实。Claude 在下一轮基于"已修复"的假设继续工作,错误层层叠加,最终难以定位。

正确做法:Claude 的描述是意图,工具的输出是事实。任何"已完成"的陈述,都要用 git diff、测试结果、类型检查结果对齐。不一致就以工具为准。

边界三:checkpoint 缺失导致无法回退

没有 Git commit,没有 /rewind checkpoint,Claude 跑偏后你只能手动一个个文件找回。

后果:回退成本极高,有时候比从头重来还贵。更糟的是,你可能根本不知道 Claude 改了哪些文件(它可能动了你没注意的配置文件)。

正确做法:高风险修改前必 commit。即使是小改动,每完成一个可验证单元就 commit 一次。commit 消息清晰(checkpoint: before Xcheckpoint: after Y verified),方便回退时定位。


控本与故障恢复是工程化的底线。底线守住,后面的 Hooks、Subagents、Workflows 才有发挥的空间;底线失守,再炫的功能都会被幻觉和超支吞掉。下一章进入代码审查与版本控制的具体工具。


上一章:Plan Mode 与重构纪律
下一章:Git 工作流与 Code Review