
0. 太长不读
今天这篇文章源于最近半年深度使用 Claude Code、两个账号每月 40 刀氪金换来的一些踩坑经验,希望能给大伙一些输入。
刚开始我也把它当 ChatBot 用,后来很快发现不对劲:上下文越来越乱、工具越来越多但效果越来越差、规则越写越长却越不遵守,折腾了一段时间,研究了 Claude Code 本身之后才意识到,这不是 Prompt 问题,而是这套系统的设计就是这样的。
这篇文章想和大伙聊聊这几个事:Claude Code 底层怎么运作、上下文为什么会乱以及怎么治理、Skills 和 Hooks 应该怎么设计、Subagents 的正确用法、Prompt Caching 的架构影响,以及怎么写一个真正有用的 CLAUDE.md。
我觉得最直接的理解方式,是把 Claude Code 拆成六层来看:

只强化其中一层,系统就会失衡,CLAUDE.md 写太长,上下文先污染自己了;工具堆太多了,选择就搞不清楚了;subagents 开得到处都是,状态就漂移了;验证这步跳过了,出了问题根本不知道是哪里挂的。
1. 它底层是怎么运行的

Claude Code 的核心不是"回答",而是一个反复循环的代理过程:
用了一段时间才意识到,卡住的地方几乎从来不是模型不够聪明,更多时候是给了它错误的上下文,或者写出来了但根本没法判断对不对,也没法撤回。
真正要关注的五个层面

对着这几个面看,很多问题就好排查了。结果不稳定,查上下文加载顺序,不是模型的事;自动化失控,看控制层有没有设计,不是 agent 太主动;长会话质量下降,中间产物把上下文污染了,换个新会话比反复调 prompt 有用得多。
2. 概念边界:MCP / Plugin / Tools / Skills / Hooks / Subagents

简单记:给 Claude 新动作能力用 Tool/MCP,给它一套工作方法用 Skill,需要隔离执行环境用 Subagent,要强制约束和审计用 Hook,跨项目分发用 Plugin。
3. 上下文工程:最重要的系统约束
很多人把上下文当"容量问题",但卡住的地方通常不是不够长,而是太吵了,有用的信息被大量无关内容淹没了。
真实的上下文成本构成

Claude Code 的 200K 上下文并非全部可用:

一个典型 MCP Server(如 GitHub)包含 20-30 个工具定义,每个约 200 tokens,合计 4,000-6,000 tokens。接 5 个 Server,光这部分固定开销就到了 25,000 tokens(12.5%)。我第一次算出这个数字的时候,真没想到有这么多,在要读大量代码的场景,这 12.5% 真的很关键。
推荐的上下文分层
说白了,偶尔用的东西就不要每次都加载进来。
上下文最佳实践
保持 CLAUDE.md 短、硬、可执行,优先写命令、约束、架构边界。Anthropic 官方自己的 CLAUDE.md 大约只有 2.5K tokens,可以参考
把大型参考文档拆到 Skills 的 supporting files,不要塞进 SKILL.md 正文
使用 .claude/rules/ 做路径/语言规则,不让根 CLAUDE.md 承担所有差异
长会话主动用 /context 观察消耗,不要等系统自动压缩后再补救

任务切换优先 /clear,同一任务进入新阶段用 /compact
把 Compact Instructions 写进 CLAUDE.md,压缩后必须保留什么由你控制,不由算法猜
压缩机制的陷阱
默认压缩算法按"可重新读取"判断,早期的 Tool Output 和文件内容会被优先删掉,顺带把架构决策和约束理由也一起扔了。两小时后再改,可能根本不记得两小时前定了什么,莫名其妙的 Bug 就是这么来的。

解决方案就是在 CLAUDE.md 里写明:
除了写 Compact Instructions,还有一种更主动的方案:在开新会话前,先让 Claude 写一份 HANDOFF.md,把当前进度、尝试过什么、哪些走通了、哪些是死路、下一步该做什么写清楚。下一个 Claude 实例只读这个文件就能接着做,不依赖压缩算法的摘要质量:
在 HANDOFF.md 里写清楚现在的进展。解释你试了什么、什么有效、什么没用,让下一个拿到新鲜上下文的 agent 只看这个文件就能继续完成任务。
写完后快速扫一眼,有缺漏直接让它补,然后开新会话,把 HANDOFF.md 的路径发过去就行。
Plan Mode 的工程价值

Plan Mode 的核心是把探索和执行拆开,探索阶段不动文件,确认方案后再执行:
探索阶段以只读操作为主
Claude 可以先澄清目标和边界,再提交具体方案
执行成本在计划确认之后才发生

对于复杂重构、迁移、跨模块改动,这样做比"急着出代码"有用多了,它能显著降低在错误假设上持续修改的概率。按两下 Shift+Tab 进入 Plan Mode,进阶玩法是开一个 Claude 写计划,再开一个 Codex 以"高级工程师"身份审这个计划,让 AI 审 AI,效果很好。
4. Skills 设计:不是模板库,是用的时候才加载的工作流
Skill 官方描述是"按需加载的知识与工作流",描述符常驻上下文,完整内容按需加载,用起来和"保存的 Prompt"差别挺大的。
一个好 Skill 应该满足什么
描述要让模型知道"何时该用我",而不是"我是干什么的",这两个差很多
有完整步骤、输入、输出和停止条件,别写了个开头没有结尾
正文只放导航和核心约束,大资料拆到 supporting files 里
有副作用的 Skill 要显式设置 disable-model-invocation: true,不然 Claude 会自己决定要不要跑
Skill 怎么做到按需加载
Claude Code 团队在内部设计中反复强调 “progressive disclosure”,意思不是让模型一次性看到所有信息,而是先获得索引和导航,再按需拉取细节:
SKILL.md 负责定义任务语义、边界和执行骨架
supporting files 负责提供领域细节
脚本负责确定性收集上下文或证据
一个比较稳定的结构长这样:
Skill 的三种典型类型
下面几个例子都来自我在开源 terminal 项目 Kaku 里的实际 Skill,比较直观。
类型一:检查清单型(质量门禁)
发布前跑一遍,确保不漏项:
| |
类型二:工作流型(标准化操作)
配置迁移高风险,显式调用 + 内置回滚步骤:
| |
类型三:领域专家型(封装决策框架)
运行时出问题时让 Claude 按固定路径收集证据,不要瞎猜:
| |
描述符写短点,每个 Skill 都在偷你的上下文空间,每个启用的 Skill,描述符常驻上下文,优化前后差距很大:
| |
还有一个很重要的 disable-auto-invoke 使用策略:
高频(>1 次/会话)→ 保持 auto-invoke,优化描述符
低频(<1 次/会话)→ disable-auto-invoke,手动触发,描述符完全脱离上下文
极低频(<1 次/月)→ 移除 Skill,改为 AGENTS.md 中的文档
Skills 反模式
描述过短:description: help with backend(任何后端工作都能触发)
正文过长:几百行工作手册全塞进 SKILL.md 正文
一个 Skill 覆盖 review、deploy、debug、docs、incident 五件事
有副作用的 Skill 允许模型自动调用
5. 工具设计:怎么让 Claude 少选错
我后面越用越觉得,给 Claude 的工具和给人写的 API 不是一回事。给人用的 API 往往会追求功能齐全,但给 agent 用,重点不是功能堆得多完整,而是让它更容易用对。
好工具 vs 坏工具

几个实用设计原则:
名称前缀按系统或资源分层:github_pr_、jira_issue_
对大响应支持 response_format: concise / detailed
错误响应要教模型如何修正,不要只抛 opaque error code
能合并成高层任务工具时,不要暴露过多底层碎片工具,避免 list_all_* 让模型自行筛选
从 Claude Code 内部工具演进学到的

我看到 Claude Code 团队内部工具的这段演进时,感觉还挺有意思。像这种需要在任务中途停下来问用户的场景,他们前后试了三种做法:
第一版:给已有工具(如 Bash)加一个 question 参数,让 Claude 在调用工具时顺带提问。结果 Claude 大多数时候直接忽略这个参数,继续往下跑,根本不停下来问。
第二版:要求 Claude 在输出里写特定 markdown 格式,外层解析到这个格式就暂停。问题是没有强制约束,Claude 经常"忘了"按格式写,提问逻辑非常脆弱。
第三版:做成独立的 AskUserQuestion 工具。Claude 想提问就必须显式调用它,调用即暂停,没有歧义。效果显著好于前两版。
下面这张图刚好能解释,为什么第三版明显更稳:

左边(markdown 自由输出)太松,模型格式随意、外层解析脆弱;右边(ExitPlanTool 参数)太死,等到退出计划阶段提问已经太晚;AskUserQuestion 独立工具落在中间,结构化且随时可调用,是这三者里最稳定的设计。
说白了,既然你就是要 Claude 停下来问一句,那就直接给它一个专门的工具。加个 flag 或者约定一段输出格式,很多时候它一顺手就略过去了。
Todo 工具的演进

早期用 TodoWrite 工具 + 每 5 轮插入提醒让 Claude 记住任务。随着模型变强,这个工具反而成了限制,Todo 提醒让 Claude 认为必须严格遵循,无法灵活修改计划。挺有意思的教训:当初加这个工具是因为模型不够强,模型变强之后它反而变成了枷锁。值得过段时间回来检查一下,当初加的限制还成不成立。
搜索工具的演进:最初用 RAG 向量数据库,虽然快但需要索引、不同环境脆弱,最重要的是 Claude 不喜欢用。改成 Grep 工具让 Claude 自己搜索后,效果显著提升。后来又发现一个顺带的好处:Claude 读 Skill 文件,Skill 文件又引用其他文件,模型会递归读取,按需发现信息,不需要提前塞进去,这个模式后来被叫做"渐进式披露"。
什么时候不该再加 Tool
本地 shell 可以可靠完成的事情
模型只需要静态知识,不需要真正与外部交互
需求更适合 Skill 的工作流约束,而不是 Tool 的动作能力
还没验证过工具描述、schema 和返回格式能被模型稳定使用
6. Hooks:在 Claude 执行操作前后,强制插入你自己的逻辑
Hooks 很容易被当成"自动运行的脚本",但我自己用下来,觉得它更像是把一些不能交给 Claude 临场发挥的事情,重新收回到确定性的流程里。
比如格式化要不要跑、保护文件能不能改、任务完成后要不要通知,这些事真不要指望 Claude 每次都自己记得。
当前支持的 Hook 点

适合 vs 不适合放到 Hooks 的
适合:阻断修改受保护文件、Edit 后自动格式化/lint/轻量校验、SessionStart 后注入动态上下文(Git 分支、环境变量)、任务完成后推送通知。
不适合:需要读大量上下文的复杂语义判断、长时间运行的业务流程、需要多步推理和权衡的决策,这些该在 Skill 或 Subagent 里。
| |
Hooks:越早发现错误,越省时间

在 100 次编辑的会话中,每次节省 30-60 秒,累积节省 1-2 小时,还挺可观的。注意限制输出长度(| head -30),避免 Hook 输出反而污染上下文。
Hooks + Skills + CLAUDE.md 三层叠加
CLAUDE.md:声明"提交前必须通过测试和 lint"
Skill:告诉 Claude 在什么顺序下运行测试、如何看失败、如何修复
Hook:对关键路径执行硬性校验,必要时阻断
用下来感觉,三样少任何一层都会有漏洞。只写 CLAUDE.md 规则,Claude 经常当没看见;只靠 Hooks,细节判断又做不了,放在一起才比较稳。
7. Subagents:派一个独立的 Claude 去干一件具体的事
Subagent 就是从主对话派出去的一个独立 Claude 实例,有自己的上下文窗口、只用你指定的工具、干完汇报结果。核心价值不是"并行",而是隔离,扫代码库、跑测试、做审查这类会产生大量输出的事,交给 Subagent 做,主线程只拿摘要,不会被中间过程污染。Claude Code 内置了 Explore(只读扫库,跑 Haiku 省成本)、Plan(规划调研)、General-purpose(通用),也可以自定义。
配置时要显式约束
tools / disallowedTools:限定能用什么工具,别给和主线程一样宽的权限
model:探索任务用 Haiku/Sonnet,重要审查用 Opus
maxTurns:防止跑飞
isolation: worktree:需要动文件时隔离文件系统
另一个实用细节:长时间运行的 bash 命令可以按 Ctrl+B 移到后台,Claude 之后会用 BashOutput 工具查看结果,不会阻塞主线程继续工作。subagent 同理,直接告诉它「在后台跑」就行。
几个常见反模式
子代理权限和主线程一样宽,隔离没有意义
输出格式不固定,主线程拿到没法用
子任务之间强依赖,频繁要共享中间状态,这种情况用 Subagent 不合适
8. Prompt Caching:Claude Code 内部架构的核心
这块我之前在很多教程里都没怎么看到有人展开讲,但它其实很影响 Claude Code 的成本结构和很多设计取舍。
工程界有句话 “Cache Rules Everything Around Me”,对 agent 同样如此,Claude Code 的整个架构都是围绕 Prompt 缓存构建的,高缓存命中率不只降低成本,也帮助创造更宽松的速率限制,Anthropic 甚至会对命中率运行告警,太低直接宣布 SEV。
为缓存设计的 Prompt Layout

Prompt 缓存是按前缀匹配工作的,从请求开头到每个 cache_control 断点之前的内容都会被缓存。所以这里的顺序很重要:
破坏缓存的常见陷阱
在静态系统 Prompt 中放入带时间戳的内容(让它每次都变)
非确定性地打乱工具定义顺序
会话中途增删工具
那像当前时间这种动态信息怎么办?别去动系统 Prompt,放到下一条消息里传进去就行。Claude Code 自己也是这么做的,用户消息里加 <system-reminder> 标签,系统 Prompt 不动,缓存也就不会被打坏。
会话中途不要切换模型
Prompt 缓存是模型唯一的。假如你已经和 Opus 对话了 100K tokens,想问个简单问题,切换到 Haiku 实际上比继续用 Opus 更贵,因为要为 Haiku 重建整个缓存。确实需要切换的话,用 Subagent 交接:Opus 准备一条"交接消息"给另一个模型,说明需要完成的任务就行。
Compaction 的实际实现

上图是 Compaction(上下文压缩)的执行流程:左边是上下文快满时的状态,中间是 Claude Code 开一个 fork 调用,把完整对话历史喂给模型,加一句"Summarize this conversation",这一步命中缓存所以只需 1/10 的价格,右边是压缩完之后,原来几十轮对话被替换成一段 ~20k tokens 的摘要,System + Tools 还在,再挂上之前用到的文件引用,腾出空间继续新的轮次。
直觉上 Plan Mode 应该切换成只读工具集,但这会破坏缓存。实际实现是:EnterPlanMode 是模型可以自己调用的工具,检测到复杂问题时自主进入 plan mode,工具集不变,缓存不受影响。
defer_loading:工具的延迟加载
Claude Code 有数十个 MCP 工具,每次请求全量包含会很贵,但中途移除会破坏缓存。解决方案是发送轻量级 stub,只有工具名,标记 defer_loading: true。模型通过 ToolSearch 工具"发现"它们,完整的工具 schema 只在模型选择后才加载,这样缓存前缀保持稳定。
9. 验证闭环:没有 Verifier 就没有工程上的 Agent
「Claude 说完成了」其实没啥用,你得能知道它做没做对、出了问题能退回来、过程还能查,这才算数。
Verifier 的层级
最低层:命令退出码、lint、typecheck、unit test
中间层:集成测试、截图对比、contract test、smoke test
更高层:生产日志验证、监控指标、人工审查清单
在 Prompt、Skill 和 CLAUDE.md 中显式定义验证
| |
写任务 Prompt 或 Skill 的时候,最好把验收标准提前说清楚。哪些命令跑完算完成,失败了先查什么,截图和日志看到什么才算过,这些越早讲明白,后面越省事。
我自己有个很简单的判断:假如一个任务你都说不清楚「Claude 怎么才算做对了」,那它大概率也不适合直接丢给 Claude 自动完成。
10. 高频命令的工程意义
这些命令说白了就干一件事:主动管理上下文,别等系统自己处理。
上下文管理
能力与治理

会话连续性与并行
几个不常见但很好用的命令
/simplify:对刚改完的代码做三维检查,代码复用、质量和效率,发现问题直接修掉。特别适合改完一段逻辑后立刻跑一遍,代替手动 review。
/rewind:不是"撤销",而是回到某个会话 checkpoint 重新总结。适合:Claude 已沿错误路径探索太久;想保留前半段共识但丢掉后半段失败。
/btw:在不打断主任务的前提下快速问一个侧问题,适合"两个命令有什么区别"这类单轮旁路问答,不适合需要读仓库或调用工具的问题。
claude -p –output-format stream-json:实时 JSON 事件流,适合长任务监控、增量处理、流式集成到自己的工具。
/insight:让 Claude 分析当前会话,提炼出哪些内容值得沉淀到 CLAUDE.md。用法是会话做了一段之后跑一次,它会指出"这个约定你们反复提到,但没有写进契约"之类的盲点,是迭代优化 CLAUDE.md 的好手段。
双击 ESC 回溯:按两次 ESC 可以回到上一条输入重新编辑,不用重新手打。Claude 走偏了、或者上一句话没说清楚,双击 ESC 修改后重发,比重新开会话省事得多。
对话历史都在本地:所有会话记录存放在 ~/.claude/projects/ 下,文件夹名按项目路径命名(斜杠变横杠),每个会话是一个 .jsonl 文件。想找某个话题的历史,直接 grep -rl “关键词” ~/.claude/projects/ 就能定位,或者直接告诉 Claude「帮我搜一下之前关于 X 的讨论」,它会自己去翻。
11. 如何写一个好的 CLAUDE.md
CLAUDE.md 在我看来更像是你和 Claude 之间的协作契约,不是团队文档,也不是知识库,里面只放那些每次会话都得成立的事。
我自己的建议其实很简单,一开始甚至可以什么都不写。先用起来,等你发现自己老是在重复同一件事,再把它补进去。加法也不复杂,输入 # 可以把当前对话里的内容直接追加进 CLAUDE.md,或者直接告诉 Claude「把这条加到项目的 CLAUDE.md 里」,它会知道该改哪个文件。

应该放什么
怎么 build、怎么 test、怎么跑(最核心)
关键目录结构与模块边界
代码风格和命名约束
那些不明显的环境坑
绝对不能干的事(NEVER 列表)
压缩时必须保留的信息(Compact Instructions)
不该放什么
大段背景介绍
完整 API 文档
空泛原则,如"写高质量代码"
Claude 通过读仓库即可推断的显然信息
大量背景资料和低频任务知识(这些放到 Skills)
高质量模板
| |
用起来其实不复杂:每次都要知道的放 CLAUDE.md,只对部分文件生效的放 rules,只在某类任务中需要的放 Skills。
让 Claude 维护自己的 CLAUDE.md
我最喜欢的一个技巧:每次纠正 Claude 的错误后,让它自己更新 CLAUDE.md:
“Update your CLAUDE.md so you don’t make that mistake again.”
Claude 在给自己补这类规则时其实还挺好用,用久了确实越来越少犯同样的错。不过也要定期 review,时间一长总会有些条目慢慢过时,当初有用的限制现在未必还适合,这件事后面第 14 节有个更系统的做法。
12. 最近自己折腾中得到的新经验
春节放假时,我用 Claude Code 做了一个开源 terminal 项目 Kaku,底层是 Rust + Lua,也带了一些 AI 能力。混合语言加上自定义配置系统,实际折腾下来反而暴露出不少典型的 agent 协作问题,顺手聊几个对我帮助比较大的经验。
环境透明比你想象中重要
Claude Code 调用的都是真实的 shell、git、package manager 和本地配置。这里面只要有一层不透明,它就只能开始猜;一旦走到猜环境这一步,可靠性通常就会掉得很快。这其实也不只是 Claude Code 的问题,很多 agent 都一样。
所以我后来很快就在 terminal 里加了个 doctor 命令,把环境状态、依赖和配置情况先统一收上来,输出一份结构化的健康报告。Claude Code 开始做事前先跑一次 doctor,确实能省掉很多"环境没搞清楚就开干"的问题。
另外我还发现,假如 CLI 本身就有 init、config、reset 这类语义清楚的子命令,Claude Code 用起来会稳不少,比让它自己去猜配置文件怎么摆要靠谱。先把状态收敛住,再暴露编辑入口,顺序一反过来就很容易乱。
混合语言项目的 Hooks 实践
两套语言、两套检查,其实挺适合用 Hooks 按文件类型分别触发:
| |
每次编辑完立刻知道有没有编译错误,比"跑了一堆才发现最开始就挂了"舒服得多。
完整的工程化布局参考
假如有同学想给自己项目配一套比较完整的 Claude Code 工程布局,可以参考这个结构,不用全做,按需裁剪:
| |
全局约束(CLAUDE.md)、路径约束(rules)、工作流(skills)和架构细节完全解耦,Claude Code 的执行稳定性会显著上升。假如你同时维护多个项目,可以把稳定的个人基线放在 ~/.claude/,各项目的差异放在项目级 .claude/,通过同步脚本分发,不同项目之间就不会互相污染了。
13. 常见反模式

14. 配置健康检查
基于文章里的六层框架,我把这套检查整理成了一个开源 Skill 项目 tw93/claude-health,可以一键检查你的 Claude Code 配置现在处于什么状态。
| |
装好之后在任意会话里跑 /health,它会自动识别项目复杂度,对 CLAUDE.md、rules、skills、hooks、allowedTools 和实际行为模式各跑一遍检查,输出一份优先级报告:需要立刻修 / 结构性问题 / 可以慢慢做。
如果你读完这篇文章想知道自己的配置离这些原则差多远,跑一次 /health 是最快的方式。
15. 结语
用 Claude Code 大概会经历三个阶段:

到了第三阶段,关注点会悄悄变掉,从「这个功能怎么用」变成「怎么让 agent 在约束下自己跑起来」,两件事感觉差很多。
有一个问题挺值得想的:假如一个任务你说不清楚「什么叫做完」,那大概率也不适合直接扔给 Claude 自主完成,验证标准本身都没有,Claude 再聪明也跑不出正确答案。
这些是半年折腾下来的一些总结,肯定还有很多没有挖掘到的地方,如果大伙有用得更 6 的技巧,欢迎告诉我。