Claude Code 不听 CLAUDE.md 时,我们该从哪里下手:用 hooks 与 InstructionsLoaded 排查配置

发布时间:2026/9/27 16:29:41
Claude Code 不听 CLAUDE.md 时,我们该从哪里下手:用 hooks 与 InstructionsLoaded 排查配置 1. 为什么 Claude Code 会“不听” CLAUDE.md你大概率遇到过这种场景项目根目录的 CLAUDE.md 里明明写了“包管理器统一用 pnpm”结果 Claude Code 在某个子包里还是给你生成了npm install或者你写了“所有测试必须带 Redis”它却直接跑了一个纯单元测试。前一天刚强调过的规则第二天就像没看见一样。先给结论这通常不是模型变笨也不是 CLAUDE.md 完全没生效。更准确的说法是CLAUDE.md 在 Claude Code 里属于上下文指令而不是不可违背的强制配置。它会在会话开始时被读取用来承载项目规则、编码标准、架构背景和常见工作流但它本质上是一条注入到上下文里的说明不是操作系统级别的门禁。这里有个关键细节很多人会忽略CLAUDE.md 的内容并不是 system prompt 的一部分而是作为 system prompt 之后的一条 user message 传给模型。模型会读、会尽量遵循但无法保证 100% 严格合规尤其在规则模糊、规则冲突、或者当前任务上下文信号很强的时候。打个比方CLAUDE.md 很像团队给新人写的 onboarding 文档。文档可以告诉新人“提交前跑测试”“接口错误格式要统一”但文档不是 Git hook也不是 CI。新人认真读了通常会照做可一旦文档写得含糊、前后矛盾或者手头任务压力大他仍然可能漏掉一条。真正能硬性拦住错误提交的是 Git hook、CI pipeline、lint rule 这些自动化机制。所以排查这类问题时别只问“为什么 Claude 不听话”。更有效的问题是四个Claude 是否真的看到了那份文件规则是否足够清晰是否有别的文件给了相反指令这条要求到底应该是软规则还是硬约束这篇就围绕这四个问题从InstructionsLoaded事件和 hooks 入手给你一套可复制的排查路径包括settings.json骨架、hooks 配置片段以及验证 CLAUDE.md 是否被加载的具体动作。2. 前置准备确认加载路径与 TaoToken 接入在动手排查之前先把两件事准备好一是确认 Claude Code 的加载路径规则二是把模型接入配置好避免把“接入问题”误判成“CLAUDE.md 问题”。Claude Code 有自己的文件查找规则。它会从当前工作目录开始向上遍历目录树查找沿途的CLAUDE.md和CLAUDE.local.md并把发现的文件拼接进上下文而不是互相覆盖。对于工作目录下方子目录里的 CLAUDE.md它们不会在启动时立刻加载而是在 Claude 读取对应子目录文件时按需加载。这对大型仓库特别重要。你在apps/web目录启动 Claude Code它会看到apps/web/CLAUDE.md也会看到apps/CLAUDE.md还可能看到仓库根目录的CLAUDE.md。但如果某条规则放在packages/payment/CLAUDE.md而当前会话从未读取过packages/payment下的文件那这份子目录规则可能还没进入上下文。此时要求 Claude 遵循支付模块的专属规则就像在会上要求同事遵循一份还没发到群里的文档。接入层面如果你用的是兼容 Anthropic 协议的网关需要把 base URL 和 API Key 配好。TaoToken 的 API 地址是https://taotoken.net/api控制台和密钥管理入口如下控制台https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentAPI Keyshttps://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content环境变量可以这样设置让 Claude Code 走这个端点export ANTHROPIC_BASE_URLhttps://taotoken.net/api export ANTHROPIC_API_KEYsk-你的密钥注意ANTHROPIC_BASE_URL不要带末尾斜杠也不要拼/v1具体以接入文档为准。配错端点会直接导致请求失败这种报错和 CLAUDE.md 无关别混在一起排查。3. 可复制配置settings.json 骨架与 hooks 片段排查 CLAUDE.md 是否生效最直接的工具是/memory命令它会列出当前会话已加载的 CLAUDE.md、CLAUDE.local.md 和 rules 文件。如果目标文件不在列表里就别急着优化 prompt先解决加载路径问题。但/memory是手动查看复杂仓库里你更需要一份自动记录。这就是InstructionsLoadedhook 的用武之地。它在 CLAUDE.md 或.claude/rules/*.md文件加载进上下文时触发既会在 session start 触发也会在会话期间文件被懒加载时触发。下面是一份可复制的settings.json骨架放在项目.claude/settings.json里{ hooks: { InstructionsLoaded: [ { hooks: [ { type: command, command: bash .claude/hooks/log-instructions.sh } ] } ], PostToolUse: [ { matcher: Edit|Write, hooks: [ { type: command, command: bash .claude/hooks/guard-generated.sh } ] } ] } }对应的日志脚本.claude/hooks/log-instructions.sh把每次加载事件追加到文件方便事后审计#!/usr/bin/env bash set -euo pipefail LOG_DIR.claude/logs mkdir -p $LOG_DIR INPUT$(cat) TS$(date %Y-%m-%d %H:%M:%S) echo [$TS] $INPUT $LOG_DIR/instructions-loaded.log exit 0再配一个拦截生成目录被误改的脚本.claude/hooks/guard-generated.sh把“不要改 src/generated”从口头提醒变成关卡#!/usr/bin/env bash set -euo pipefail INPUT$(cat) FILE_PATH$(echo $INPUT | python3 -c import sys,json; djson.load(sys.stdin); print(d.get(tool_input,{}).get(file_path,)) 2/dev/null || true) if [[ $FILE_PATH *src/generated* ]]; then echo BLOCK: src/generated 为自动生成目录禁止手工编辑 2 exit 2 fi exit 0提示hook 脚本记得加执行权限chmod x .claude/hooks/*.sh。退出码2在 Claude Code 里表示阻断该工具调用退出码0表示放行。4. 验证请求确认 CLAUDE.md 到底有没有被加载配置写好后先做一次最小验证确认日志真的在记录。第一步启动 Claude Code随便让它读一个文件触发会话初始化claude第二步在会话里输入/memory看目标 CLAUDE.md 是否出现在列表里。如果没出现直接跳到第 5 节的排查清单。第三步退出会话查看日志文件cat .claude/logs/instructions-loaded.log正常输出应该能看到类似这样的记录包含加载的文件路径和触发时机[2025-01-15 10:22:31] {event:InstructionsLoaded,file:/repo/CLAUDE.md,trigger:session_start} [2025-01-15 10:23:05] {event:InstructionsLoaded,file:/repo/apps/api/CLAUDE.md,trigger:lazy_load}如果日志里只有根目录的 CLAUDE.md没有子目录的说明子目录规则还没被懒加载。这时候你可以主动让 Claude 读取相关目录文件比如请先读取 apps/api/CLAUDE.md 和 apps/api/package.json再开始修改接口。再跑一次日志就能看到lazy_load记录出现了。这一步能帮你区分两类问题一类是规则根本没加载路径放错、启动目录不对另一类是规则加载太晚Claude 前几轮已经做完设计决策后面才读到子目录规则。对于后者解决办法是把必须全局生效的规则上移到项目根目录或者养成任务开始时先让 Claude 读相关目录文件的习惯。5. 本篇常见错排查排查时按下面这个顺序走基本能覆盖 90% 的“CLAUDE.md 不生效”场景。第一文件位置放错。项目级 CLAUDE.md 可以放在./CLAUDE.md或./.claude/CLAUDE.md个人偏好放项目根目录的CLAUDE.local.md建议加进.gitignore。有人把规则写进./.claude/CLAUDE.md有人放./CLAUDE.md路径不统一时Claude 只能凭当前上下文猜。第二启动目录不对。在apps/web启动就不会自动加载packages/payment的规则。用/memory和InstructionsLoaded日志交叉确认。第三规则写得太抽象。“保持代码优雅”“遵循最佳实践”这类话人类读没问题模型读起来没有可操作的判断边界。官方给的典型对照是Use 2-space indentation比format code nicely有效得多。把“注意错误处理”改成“所有 controller 捕获业务异常时统一返回 ApiErrorResponse日志必须包含 requestId禁止打印 access token”遵循稳定性会明显提升。第四规则冲突。CLAUDE.md 的加载是拼接而非覆盖。根目录写“用 npm”子项目写“用 pnpm”个人 local 文件写“用 yarn”Claude 看到的是三套指令。这就像 Spring Boot 里 application.yml、环境变量、启动参数叠在一起冲突时不一定报错但行为会让人困惑。稳妥做法是根目录写全局原则子目录写局部例外并显式标注优先级。第五该硬执行的动作只写了软提醒。每次编辑后格式化、commit 前跑测试、危险命令拦截这些必须发生在固定时机的动作应该交给 hooks而不是靠一句 CLAUDE.md 提醒。PostToolUse绑定Edit|Write后自动格式化PreToolUse拦截危险命令才是确定性机制。第六需要 system prompt 级别的要求没用对 flag。如果某条要求确实要提升到 system prompt 层级Claude Code 提供了--append-system-prompt和--append-system-prompt-file。但要注意这些 flag 只作用于当前 invocation更适合脚本化、CI 场景不适合普通交互式会话。追加型 flag 会保留默认的工具指导和安全说明替换型--system-prompt会丢掉这些默认内容要非常谨慎。第七CLAUDE.md 写太长。官方建议每个 CLAUDE.md 控制在 200 行以内更大的文件会消耗更多上下文也可能降低指令遵循度。一页纸的规则更容易被稳定遵循几十页的规范反而会被压缩成模糊印象。6. 把软约束和硬机制分层问题就从玄学变工程排查到最后你会发现Claude Code 的配置不是单点魔法而是一套层级系统。CLAUDE.md 负责提供项目上下文rules 负责模块化和路径作用域hooks 负责固定生命周期动作CLI system prompt flags 负责单次调用里的高优先级补充settings 负责权限和环境。当 Claude 没有遵循 CLAUDE.md按这个路径走先运行/memory确认加载情况再确认作用域和路径然后检查表达是否具体、是否存在冲突接着判断是否需要迁移到 hook最后才考虑是否需要 system prompt 层级。大型仓库、路径规则、懒加载子目录都建议打开InstructionsLoaded审计没有日志的规则系统排查起来只能靠猜。如果你在验证模型行为、对比不同配置下的遵循效果可以直接用模型对话入口快速试模型对话https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content如果你是要长期跑编码任务、Agent 工作流建议用 Coding Plan把接入和额度管理固定下来Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content接入和密钥相关的配置统一在 API Keys 和接入文档里查API Keyshttps://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content我试过把团队规范一股脑塞进 CLAUDE.md结果就是又长又冲突遵循率反而下降。后来拆成三层——根目录写全局原则、子目录写局部例外、固定动作交给 hooks——Claude Code 的行为才从“偶尔聪明”变成稳定可用。地图清楚护栏可靠日志可查剩下的就是正常写代码了。