CLAUDE.md 管倾向,Hooks 才是铁律
Claude Code 开新会话会把昨天对齐的事忘掉。CLAUDE.md 就是用来补这件事的。但很多人把它写成总开关,然后发现「提交前必须跑测试」「不许碰 .env」还是会漏。
原因很具体:CLAUDE.md 是在系统提示之后,以用户消息的形式注入的上下文。模型会尽量照做,不保证每次都做到。模糊、矛盾、被长文淹没的句子,漏得更快。
硬约束要交给 Hooks。钩子绑在生命周期事件上,到点就跑脚本,不经过「这次想不想做」的判断。
站里已有一份可直接改的 CLAUDE.md 约束提示词。这篇只讲它和 Hooks 的分界,以及文件该放哪、为什么有时不生效。
命令和字段会随版本变。下面以当前仍公开的记忆和 Hooks 机制为准。你这台机器对不上时,在会话里跑 /memory、/hooks,或看 claude --version,不要按旧教程硬套。
什么时候写文件,什么时候写钩子
先看这句话里有没有「必须」「每次」「绝对不能」。
- 有,用 Hooks。典型是写完文件就格式化、提交前跑测试、拦下
rm -rf、不许改某个目录。 - 没有,只是惯例和偏好,写进
CLAUDE.md。包管理器用 pnpm、注释用中文、金额用整数存分,都属于这一类。
只在改某一块代码时才用得上的多步流程,也不该塞进 CLAUDE.md。它每次会话都会全量加载。这种流程做成 Skill,碰到相关任务再读。怎么选 MCP、Skills、Hooks,另见 MCP、Skills、Hooks 怎么选。
文件放哪,以及它们不会互相覆盖
现在不是「三层、后面覆盖前面」。作用域有四级,发现到的文件会拼进上下文,谁也不覆盖谁。越靠近你启动目录的内容越靠后读到。同一级里,CLAUDE.local.md 排在 CLAUDE.md 后面。
| 作用域 | 位置 | 谁能用 |
|---|---|---|
| 组织策略 | Linux / WSL:/etc/claude-code/CLAUDE.md;macOS:/Library/Application Support/ClaudeCode/CLAUDE.md;Windows:C:\Program Files\ClaudeCode\CLAUDE.md | 这台机器上的人,个人改不掉 |
| 用户 | ~/.claude/CLAUDE.md | 只有你,所有项目 |
| 项目 | ./CLAUDE.md 或 ./.claude/CLAUDE.md | 跟着仓库走的团队 |
| 本地 | ./CLAUDE.local.md | 只有你,当前项目,应进忽略列表 |
所以子目录里再写一条「我们这里不用 pnpm」,推翻不了父目录那条「一律用 pnpm」。两条会同时在上下文里。它们一打架,模型就可能随机挑一条。
从当前工作目录往上找。子目录里的 CLAUDE.md 不会在启动时全部读进来,而是等 Claude 真的读那个目录下的文件时再带上。大仓库靠这个避免一上来把上下文撑满。上层仓库夹带了别的团队的说明、干扰当前工作,用 claudeMdExcludes 按路径跳过。
想确认某条规则到底有没有被读到,跑 /memory。列表里没有,就是这次会话根本没看到它。
钩子长什么样
Hooks 写在 settings.json 的 hooks 字段。项目共享放 .claude/settings.json,只给自己用放 .claude/settings.local.json 或 ~/.claude/settings.json。不要把 MCP 服务器写进这个文件。
现行结构是两层:外层 matcher 决定对哪个工具生效,内层 hooks 才是要跑的命令。很多旧文把 command 和 matcher 写成一层,配上去不生效。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "prettier --write \"$CLAUDE_FILE_PATH\""
}
]
}
]
}
}要拦住一次操作,用 PreToolUse。脚本从标准输入读到即将执行的工具和参数,用退出码或约定的 JSON 表态。exit 2 会拒绝这次调用,并把 stderr 回给模型。写在 CLAUDE.md 里的「千万别删生产库」,只是一句嘱咐。匹配到危险命令就拒绝的钩子,是这次根本跑不起来。
早年教程常只列 PreToolUse、PostToolUse、Stop、SessionStart、SessionEnd。现在事件比这多,日常先用前两个。完整列表以你当前版本的 /hooks 和官方 Hooks 文档为准,不要按五年前的五个事件来设计流程。
自动记忆别和 CLAUDE.md 混用
自动记忆是 Claude 自己写的笔记,不是你提交的规范。需要较新的 Claude Code(公开说明里是 v2.1.59 起),默认开启。位置在本机 ~/.claude/projects/<项目>/memory/,不进 Git,也不跟云会话自动同步。
每次会话只加载索引 MEMORY.md 的前 200 行或前 25KB,先到哪个算哪个。主题文件平时不加载,需要时再读。
用法上分开两句话:
- 「记住,这个项目用 pnpm」会进自动记忆。
- 「把这条写进 CLAUDE.md」才会改你能提交的那份说明。
不放心就在 /memory 里关,或在设置里写 "autoMemoryEnabled": false。
文件太长,规则反而更容易被跳过
单个 CLAUDE.md 建议压在 200 行左右。这不是硬限制,是长度和遵守度的交换:越长,占的上下文越多,后半段越容易被忽略。@路径 只是把别的文件拼进来,不省上下文,被引用的内容启动时照样全量加载,嵌套大约到四层。
按目录生效的规矩放到 .claude/rules/。文件头用 paths 声明之后,只有碰到匹配文件才进上下文。没写 paths 的规则会常驻。
---
paths:
- "src/api/**/*.ts"
---
# API
- 每个端点先做输入校验
- 错误响应用同一套结构/compact 之后也有差别。项目根的 CLAUDE.md 会从磁盘再读一遍。子目录里那份不会自动重注入,要等下次读到那个目录。只在对话里说过、没写进文件的,压缩后就没了。
/init 可以生成初稿。已经有 CLAUDE.md 时它不会覆盖,只给修改建议。仓库里已有 AGENTS.md 时,它会参考再生成。Claude Code 本身不读 AGENTS.md,要共用就在 CLAUDE.md 里写一行 @AGENTS.md,下面再补 Claude 专用的几条。
今天就可以改的三件事
- 打开
/memory,确认项目说明真的被加载了。 - 把「必须每次发生」的动作从
CLAUDE.md挪到PreToolUse或PostToolUse。 - 个人口味放
~/.claude/CLAUDE.md或CLAUDE.local.md,仓库里只留这个项目独有的事实。
给人看的背景、安装步骤、为什么这么设计,不要写进这份文件。那是 README 的事,见 CLAUDE.md 别写成第二份 README。