CLAUDE.md 到底该写什么
更新·markdown 原文
我把官方文档里散落在几页的规则接到了一起,再对着一份被真实项目打磨过的样本讲取舍。绝大多数 CLAUDE.md 写的是「请使用 TypeScript」「代码要有注释」——喂进去等于没喂,这篇讲为什么,以及该换成什么。
先纠正一个认知
CLAUDE.md 不是配置,是上下文。
官方文档写得很直白:它作为 system prompt 之后的一条 user message 送进去,不是 system prompt 的一部分。Claude 会读,会尽量照做,但没有强制遵守的保证——尤其是含糊的、互相冲突的指令。
这一条解释了「为什么我的 CLAUDE.md 老是不管用」的大半:
- 两条规则打架,Claude 会任意挑一条,不会报错。
- 写得越含糊,遵守率越低。「格式化代码」不如「用 2 空格缩进」。
- 必须挡住的动作,写进 CLAUDE.md 是挡不住的——那要用
PreToolUsehook。hook 是代码,在固定的生命周期节点执行,跟 Claude 怎么想无关。
分工是清楚的:settings 做技术强制,CLAUDE.md 做行为引导。 禁止某个工具/命令/路径用 permissions.deny;代码风格和约定用 CLAUDE.md。
该写什么:官方给了一条判据
/doctor 的体检会给已提交的 CLAUDE.md 提修剪建议,它的取舍标准就是这个问题的答案:
删掉 Claude 能从代码库自己推出来的——目录结构、依赖列表、架构概述。 留下坑、理由、以及跟工具默认不一样的约定。
「src/api/handlers/ 放 API 处理器」这类话,Claude ls 一下就知道了,写进去只是花 token。真正值钱的是它看代码看不出来的部分:为什么这里不能用那个库、哪个字段的语义跟名字不符、上次改这块出了什么事。
什么时候该往里加,官方列了四个触发条件,都很好判断:
- Claude 同一个错犯了第二次
- code review 抓到一件它本该知道的事
- 你又在对话里打了一遍上次也打过的纠正
- 一个新同事需要同样的上下文才能干活
反过来,多步流程或只在代码库某一块才用得上的东西别往里塞——前者进 skill,后者进 path-scoped rule。
一份真样本
这个站自己的说明书(AGENTS.md,原文在这儿提到的那种)是按上面那条判据长出来的。摘几段说明形状:
红线写成一句话,不解释。
🔴 只做价格。 货好不好不买来测就不可知,我们没在测。任何评分、可信度、逆向标注、质量榜单全部出局。
判据比原则有用。 原则是「要仔细核对」,判据是可执行的:
自动判据:price≠1或model_ratio×2 ≠ 官方价→ 脚本值默认不可信。
把踩过的坑固化成挡得住的规则,带日期和代价。
2026-07-17 一天里因此错了四次:站名、「+6% 税」、「不开发票」、缺 codex-sale。「已知未修」单独一节。 哪些数是可疑的、为什么还没修、修的优先级——这比假装一切正常有用得多,Claude 读到之后不会拿那几个数当结论。
共同点是:这些话没有一句能从代码里推出来。
放哪、按什么顺序加载
四层,从宽到窄,后加载的在上下文里出现得更晚:
| 层 | 位置 |
|---|---|
| 组织托管 | macOS /Library/Application Support/ClaudeCode/CLAUDE.md;Linux/WSL /etc/claude-code/CLAUDE.md;Windows C:\Program Files\ClaudeCode\CLAUDE.md |
| 用户 | ~/.claude/CLAUDE.md |
| 项目 | ./CLAUDE.md 或 ./.claude/CLAUDE.md |
| 本地私有 | ./CLAUDE.local.md(记得进 .gitignore) |
另外还有一层目录树:Claude Code 从当前工作目录往上走,沿途每一级的 CLAUDE.md 和 CLAUDE.local.md 全部拼进上下文——不是覆盖,是拼接。顺序从文件系统根往下,所以离你启动位置最近的那份读在最后。每一级里 CLAUDE.local.md 排在 CLAUDE.md 之后。
当前目录以下的子目录里的 CLAUDE.md 不在启动时加载,等 Claude 真去读那个目录里的文件时才带进来。
monorepo 里被别的团队的 CLAUDE.md 污染,用 claudeMdExcludes(glob,可以放在 .claude/settings.local.json 里只对自己生效)。组织托管那层排除不掉。
200 行
官方给的目标是每个 CLAUDE.md 控制在 200 行以内。超了两件事一起变差:占的 context 更多,遵守率更低。
三个减法,效果不一样:
- `@path` 导入 —— 只解决组织问题,不省 context。被导入的文件在启动时照样全量加载。递归最多四跳;路径相对于写导入的那个文件,不是工作目录。想在文字里提一个路径又不想触发导入,用反引号包起来。
- `.claude/rules/` + `paths:` frontmatter —— 这个真省。带
paths的规则只在 Claude 读到匹配文件时才进上下文。 - 搬进 skill —— 只在被调用时加载。多步流程都该走这条。
---
paths:
- "src/api/**/*.ts"
---
# API 开发规则
- 所有端点必须做输入校验
- 用统一的错误响应格式还有个小技巧:块级 HTML 注释(`<!-- 给维护者的话 -->`)在注入前会被剥掉,不花 token。留给人看的注记写在这里面。
另一半:auto memory
Claude 自己写的那份,跟 CLAUDE.md 是两个系统,都在每次会话开头加载。
| CLAUDE.md | auto memory | |
|---|---|---|
| 谁写 | 你 | Claude |
| 内容 | 指令和规则 | 它学到的东西、模式 |
| 位置 | 见上表 | ~/.claude/projects/<项目>/memory/ |
| 加载量 | 全量 | MEMORY.md 的前 200 行或前 25KB,取先到的 |
MEMORY.md 是索引,细节在同目录的主题文件里,那些不在启动时加载,Claude 需要时自己读。超出限额的部分下次加载直接丢掉——所以它被要求把索引压短。
按仓库存,同一个 git 仓库的所有 worktree 共用一份。关掉:/memory 里的开关,或者 autoMemoryEnabled: false,或者环境变量 CLAUDE_CODE_DISABLE_AUTO_MEMORY=1。
三个命令
- `/init` —— 让它自己读代码库生成一份初稿。已经有 CLAUDE.md 的话它提改进建议,不覆盖。
CLAUDE_CODE_NEW_INIT=1开多阶段交互流程:先问你要不要 skill 和 hook,再派子代理探代码库,最后给你一份可审的提案再落盘。 - `/context` —— 看到底加载了哪些。「Memory files」那一栏里没有的文件,Claude 就是看不见。排查「为什么不听话」第一步跑这个,不是改文案。
- `/memory` —— 列出各层的记忆文件(包括还不存在的),选一个直接打开编辑。
压缩之后
长会话压缩(/compact 或自动触发)之后,你的指令活下来多少取决于它是怎么加载进来的:
- 项目根 CLAUDE.md:从磁盘重新注入,活。
- auto memory:重新注入,活。
- 带 `paths:` 的规则:丢,直到再读到匹配文件。
- 子目录里的嵌套 CLAUDE.md:丢,直到再读那个目录里的文件。
只在对话里说过的话,压缩之后就没了。所以「说过一次就该记住」的东西,得落到文件里——上下文怎么管那篇讲全了这张表。