第 12 章 Token 控本与故障恢复
控本靠两件事——精确上下文(不喂无关文件)+ 选对模型与档位;故障恢复靠两件事——checkpoint 纪律 + 对抗幻觉的纪律。这两组纪律其实是同一个问题的两面:都是上下文管理。上下文管得住,token 自然降,幻觉自然少,失败也能回退;上下文管不住,钱花得快,bug 也藏得深。
12.1 结论:控本与故障恢复同源
很多人把"省 token"和"防故障"当成两件事,分别优化。这是错的。
Claude Code 的 token 消耗,本质上是无关状态占据上下文:过大的 CLAUDE.md、一次性塞进来的整个仓库、长日志、lockfile、已排除的假设、旧测试输出。这些噪音不仅贵,还互相干扰,让模型在第 40 轮之后开始遗忘你第 3 轮说的约束。
故障的根因也一样:上下文污染 + 幻觉累积。Claude 凭记忆而非凭文件说话、交叉验证缺失、checkpoint 缺失导致无法回退——每一次幻觉都在为下一次幻觉铺路。
所以本章不讲"省钱技巧",而是讲一条贯穿始终的纪律:管好上下文,钱和可靠性一起到手。这条纪律有四个抓手:
| 抓手 | 控本作用 | 故障恢复作用 |
|---|---|---|
| 精确投喂 | 不花冤枉钱 | 减少幻觉输入 |
| 模型/档位选型 | 把简单任务交给便宜档 | 复杂任务用强模型降失败率 |
| checkpoint | — | 失败可回退 |
| 对抗幻觉 | — | 不让错误累积成事实 |
下面分四块展开,最后给出常见报错与失败边界。
12.2 用 /cost 与 /context 量化消耗
命令
/cost查看当前会话累计 token 消耗与折算费用。
/context查看上下文占用分布——哪些文件、哪些工具输出、哪些 skill 占了大头。
预期输出(示例,非真实转录)
> /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:
检查这个项目的登录问题 @.@. 会把整个工作目录的文件信息塞进上下文,Claude 的反应不是"更聪明",而是"更迷茫":它要在几百个文件里找线索,token 花在无关扫描上,幻觉也从这些噪音里长出来。
更好的 Prompt:
调查登录后刷新页面丢失会话的问题。
入口文件:
- @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 主要提供目录树信息。前者精度高,后者范围广。优先用前者。
原则二:先画最小依赖图,再深入
大型仓库里,不要说"读完再说"。用两阶段探索:
阶段 1,只搜索不修改:
- 找到 refreshSession 的定义和直接调用者
- 找到相关测试
- 找到共享 token contract
- 最多读取 8 个文件
输出候选文件清单和选择理由,不要直接改代码。确认清单后:
阶段 2,只读取你列出的 5 个核心文件。
给出失败路径和最小修复方案,不要扩展到其他模块。给探索设文件预算不是为了机械限制,而是防止 Claude 把"理解代码库"误解成"扫描全部代码"。
原则三:排除清单写进 CLAUDE.md
把"永远不要读"的目录写进项目 CLAUDE.md,一次声明长期生效:
## 上下文排除
不要读取以下目录,除非任务明确要求:
- dist/、build/、.next/、coverage/
- node_modules/、vendor/
- *.lock、*.min.js、*.map
- fixtures/snapshots/、__snapshots__/
- 大型生成文件(>1000 行的自动生成代码)这比每次在 Prompt 里重复"不要读 dist"省力,也更可靠——CLAUDE.md 是稳定上下文,每次启动自动加载。
原则四:任务切换就清空
完成"支付 Bug 修复"后,不要在同一会话继续"设计营销落地页"。两个无关任务的上下文互相污染,既贵又容易让模型把上一个任务的约束带到新任务里。
验证
/context前几项应当都是当前任务的核心文件。如果出现上一个任务的残留(旧 diff、旧测试输出、旧搜索结果),说明该 /clear 或 /compact 了。
失败边界
@文件不是越多越好。投喂 20 个文件让 Claude"自己挑",不如先让它搜出 5 个核心文件再投喂。- 排除清单不要写得太激进。如果 Claude 真的需要读
package.json来确认依赖版本,你却把整个根目录排除了,它会瞎猜。 - 两阶段探索的"文件预算"是软约束,Claude 可能略超。如果它读了 12 个文件而不是 8 个,不要恐慌,看它读得对不对;如果它读了 40 个,就是失控,按 12.7 的幻觉对抗流程处理。
12.4 模型与档位选型
模型选错,再省 token 也是浪费。用 Opus 跑格式化是杀鸡用牛刀,用 Haiku 做架构决策是让小学生写论文。
/model:四档模型
/model进入选择器,或直接 /model sonnet 切换。2026-07-08 核对(v2.1.202+)的模型基线:
| 模型 | 定位 | 适用场景 | 成本量级 |
|---|---|---|---|
| Opus 4.8 | 旗舰推理 | 架构决策、复杂重构、跨文件一致性 | 高 |
| Fable 5 | 长程推理 | 长上下文研究、多步骤规划 | 中高 |
| Sonnet 5 | 均衡主力 | 日常编码、Bug 修复、代码审查 | 中 |
| Haiku 4.5 | 轻量快速 | 格式化、简单重命名、批量小任务 | 低 |
/effort:六档推理深度
/effort档位:low → medium → high → xhigh → max → ultracode。档位越高,模型思考越深,token 消耗越大,但复杂问题的成功率也越高。
| 档位 | 适用 | 注意 |
|---|---|---|
| low | 简单替换、格式化 | 复杂逻辑会出错 |
| medium | 日常编码默认档 | 大多数任务用这个 |
| high | 跨文件改动、Bug 排查 | 略贵 |
| xhigh/max | 架构设计、疑难 Bug | 仅复杂任务 |
| ultracode | 极限推理(research preview) | 最贵,留给硬骨头 |
/fast:Fast Mode
/fast切换 Fast Mode(基于 Opus 4.8/4.7):速度约 2.5x,成本约 1/3。适合迭代快、容错高的任务——原型探索、批量小改、跑验证。不适合最终交付——关键代码、安全审查仍用完整 Opus。
选型矩阵
| 任务类型 | 模型 | 档位 | Fast |
|---|---|---|---|
| 格式化、重命名 | Haiku 4.5 | low | 可 |
| 日常 Bug 修复 | Sonnet 5 | medium | 可 |
| 代码审查(主审) | Sonnet 5 | high | 否 |
| 架构设计、Plan Mode | Opus 4.8 | xhigh | 否 |
| 疑难 Bug、跨模块重构 | Opus 4.8 | max/ultracode | 否 |
| 长上下文研究、代码考古 | Fable 5 | high | 视情况 |
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:同任务继续,带指令压缩
/compact 保留:
- 当前 Bug 的已确认根因
- 修改过的文件清单
- 尚未通过的测试及最新错误信息
- 用户明确约束和禁止事项
- 下一步验证命令
丢弃:
- 已排除的假设
- 旧测试输出
- 重复的文件摘要为什么写聚焦指令:压缩不是无损归档。默认压缩会丢掉它认为不重要的东西,而它认为不重要的,可能恰恰是你下一步需要的。明确告诉它"留什么、丢什么",才能保住后续行动所必需的状态。
/clear:换任务,带标签清空
/clear payment-timeout-fixed参数是给即将被清除的旧对话打标签,方便日后用 /resume 选择器找回。新会话仍会加载项目级 CLAUDE.md,但不会携带上一任务的临时讨论。
/rewind:回到 checkpoint
/rewind进入交互式选择,回到此前某个 checkpoint:可只回退对话、只回退代码、二者都回退,或从某条消息开始把后续压缩成摘要。别名 /checkpoint、/undo。这些都是菜单里的回退目标,不是命令行命名参数。
适用场景表
| 操作 | 使用场景 | 不适用场景 |
|---|---|---|
/compact | 同任务继续,历史变长 | 换了完全不同的任务 |
/clear | 开始无关新任务 | 同任务还有明确未完成步骤 |
/resume | 回到旧任务继续 | 当前任务未结束 |
/rewind | Claude 跑偏了,回到最近正确点 | 想保留最近所有探索 |
验证
/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 习惯
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 跑偏时的标准停止顺序:
- 按
Esc中断当前动作。 git diff查看已发生变化。- 明确收缩范围和禁止事项,重新下指令。
- 若已无法挽回,
git checkout -- <file>或git reset --hard HEAD回到 checkpoint。
注意第 4 步:git reset --hard 是破坏性操作,会同时删除任务开始前的未提交改动。属于 CLAUDE.md 红线边界,即使有 auto-accept 也要二次确认。更安全的做法是:
git stash push -m "wip: claude diverged" -- src/
git checkout -- src/或直接让 Claude 只回退它本轮的改动:
只撤销你在本轮对 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:
session.ts 里那个刷新逻辑有什么问题?这等于让 Claude 凭记忆描述它"记得"的代码。即使文件之前读过,也可能记错。
更好的 Prompt:
读 @apps/web/src/auth/session.ts,定位 refreshSession 函数,
指出它在 token 过期时的处理逻辑,引用具体行号。强制它读文件、引用行号。如果它说"大概在第 X 行"而不读文件,打断它,要求重新读。
防线二:交叉验证关键事实
Claude 说"这个 API 已废弃"、"这个函数没有调用者"、"这个配置项不存在"时,不要直接信。让它用工具验证:
用 grep 搜索 refreshSession 的所有调用者,列出文件和行号。
用 git log 确认这个 API 最近一次变更的时间和原因。交叉验证的纪律:凡是可以用工具验证的事实,就不接受凭记忆的陈述。
防线三:CLAUDE.md 记录准确 API 名
Claude 会记错 API 名、库名、配置键。把项目中"容易记错但必须准确"的东西写进 CLAUDE.md:
## 关键 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 章)。
/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"写完整个模块"。拆成两步:
步骤 1:只输出模块骨架——函数签名、类型定义、注释说明每个函数做什么。
不要写实现体。输出后停下,等我确认。
步骤 2(确认后):逐个函数填充实现,每个函数写完跑一次类型检查。好处:骨架阶段 token 少,错了便宜改;实现阶段有了正确骨架,出错率低。比一次写完再返工省 30-50% token。
策略二:子代理并行,纵向切分
大规模重构、多模块独立任务时,用子代理并行。每个子代理有独立上下文,互不干扰,主上下文只收结论。
用 3 个子代理并行:
- Agent A:重构 src/auth/,独立 worktree
- Agent B:重构 src/api/,独立 worktree
- Agent C:更新测试,独立 worktree
各自完成后回主上下文合并。这是第四部分(第 19 章)的主题,这里只点明:子代理并行不仅快,而且每个代理的上下文更短、更聚焦,token 总量往往低于单代理长会话。因为长会话有累积噪音成本,短会话没有。
Print mode 的预算与轮次保险
CI 或脚本中必须设上限:
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、旧测试输出占据了窗口。
解决:
/context看占用分布。- 能丢的用
/compact 带指令压缩。 - 换了子任务就
/clear。 - 超长任务拆成子代理(第 19 章)。
失败边界:不要在上下文已溢出时还硬撑。继续对话只会让幻觉更严重。先压缩或清空,再继续。
报错二:权限烦(频繁弹确认)
现象:每跑一个命令都弹权限确认,打断节奏。
根因:权限规则没配好,或用了过窄的 allow 模式。
解决:
- 用
/fewer-permission-prompts扫描历史,自动生成白名单(第 05 章)。 - 读操作全部 allow:
Bash(git diff *)、Bash(git status *)、Bash(npm test *)、Bash(npm run lint *)。 - 写操作按项目约定放开,破坏性操作永远 deny。
- 长会话用
--permission-mode acceptEdits,但显式 deny 危险模式(第 05 章)。
失败边界:acceptEdits 不只自动批准文件编辑,还可能自动批准 mkdir、touch、rm、mv、sed 等工作目录文件命令。即使开了 acceptEdits,也要 deny Bash(rm -rf *)、Bash(git reset *)、Bash(git push *) 等。
报错三:命令执行失败
现象:Claude 跑的命令报错,它开始反复猜测参数或路径。
根因:Claude 凭记忆猜命令,而非先读 package.json / Makefile 确认实际脚本名。
解决:
- 把项目实际命令写进 CLAUDE.md(
npm test、npm run lint:fix、make build)。 - 让 Claude 失败时先读
package.json的 scripts 段,再重试。 - 复杂命令用
--tools限制,或封装成 MCP 工具/自定义 skill(第 16 章)。
失败边界:不要让 Claude 反复试同一个错命令。连续失败 2 次就打断,让它读配置文件确认,而不是继续猜。
报错四:Claude 幻觉(凭记忆说话)
现象:Claude 说"这个函数不存在"、"这个 API 已废弃"、"我已经修改了 X",但实际并非如此。
根因:LLM 的固有幻觉,长上下文下更严重。
解决:走 12.7 的四道防线:
- 强制读文件,引用行号。
- 关键事实用 grep/git log 交叉验证。
- 易错 API 名写进 CLAUDE.md。
- 最终用
/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 X、checkpoint: after Y verified),方便回退时定位。
控本与故障恢复是工程化的底线。底线守住,后面的 Hooks、Subagents、Workflows 才有发挥的空间;底线失守,再炫的功能都会被幻觉和超支吞掉。下一章进入代码审查与版本控制的具体工具。
上一章:Plan Mode 与重构纪律
下一章:Git 工作流与 Code Review