写进 CLAUDE.md 的「禁止」,为什么有时不灵
很多人把项目约定一股脑塞进 CLAUDE.md:命名风格、目录习惯,以及「不要改 .env」「不要 force-push」「不要外联未知地址」。风格类提示通常够用;真正要命的硬规则,却偶发失效——尤其是长工具循环之后,或会话做完一次 compact 压缩之后。
这不是「你没写清楚」那么简单。CLAUDE.md 本质是软提示:它进入上下文,引导 Claude 怎么写、怎么拆任务。上下文变长、被压缩、或中途插入大量工具输出时,模型对某一条「禁止」的注意力会下降。你事后问它,它可能还记得文件里写过;但当时那次 Bash,已经跑出去了。
社区里也有人把分工说得很直白:CLAUDE.md 管风格;必须每次生效的硬规则,放到 hooks。 本文就按这个分工,在一台常开的新加坡 Mac mini 上落地——不重复「升级后怎么冒烟测试」那篇,只讲什么该进 hooks、怎么写、怎么验证真的挂上了。
Claude 账号由你自己开通和管理;机器提供稳定合规的使用环境。下列步骤以当前 Claude Code 的 hooks 机制为准,字段名以你本机 /hooks 与官方文档为准。
一张表:软提示 vs 硬门
| 放这里 | 适合什么 | 不适合什么 | |--------|----------|------------| | CLAUDE.md | 命名、目录约定、评审口吻、默认分支策略说明、文档风格 | 「绝对不能」类安全闸:写密钥、危险 shell、推送规则 | | Skills / 参考文档 | 按需查阅的长说明、命令手册 | 每次工具调用前的强制拦截 | | PreToolUse hooks | 写文件前扫描、Bash 命令模式匹配、推送目标检查 | 事后才发现的审计(那是 PostToolUse) | | 权限 deny 列表 | 粗粒度禁用某类工具 | 复杂变体(伪装参数、间接调用)——往往不如 hook 稳 |
经验法则:
- 漏一次可以接受、下次提醒就行 →
CLAUDE.md/ Skills。 - 漏一次就伤数据或密钥 →
PreToolUsehook,失败要能拦住(exit 2或 JSON deny)。 - 只想事后记账 →
PostToolUse;它拦不住已经发生的动作。
为什么长循环和 compact 会让软提示变脆
典型路径:
- 会话开始时,
CLAUDE.md完整进上下文,硬规则看起来都在。 - 几十轮读文件、跑测试、改代码后,上下文被工具输出挤满。
- compact / 摘要会保留任务目标,但某一条否定句不一定稳稳留在「下一步工具调用」的注意力里。
- 模型仍可能「大体按风格写」,却在某次 Bash 上忘记「禁止某类命令」。
另外,若你在同一次会话里改过 CLAUDE.md(或 MEMORY 类文件),旧版本 Claude Code 在 compact 后仍可能继续用压缩前的旧副本。较新的版本(例如 2.1.311 一带)修过这类「压缩后仍用旧 CLAUDE.md」的问题。实务上:硬规则不要只赌「文件改了就立刻全局生效」——拦截逻辑放 hooks;软文档改完后必要时新开会话,并先确认版本:
claude --version
# 或你本机等价命令;把版本记进项目笔记
版本过旧就先升级,再谈「改完 CLAUDE.md 是否被 compact 吃掉」。升级之后,再用你已有的冒烟命令确认 hooks 仍注册——那是另一篇的清单,这里不展开。
在新加坡 Mac mini 上落地:三层目录
把权威环境固定在一台整机独享的常开机器上,避免笔记本 A 改了 hooks、笔记本 B 还在靠一份过时的 CLAUDE.md。
mkdir -p ~/work/your-repo/.claude/hooks
cd ~/work/your-repo
建议分层:
- 根目录
CLAUDE.md:只留短的风格与导航(「怎么命名」「文档放哪」「默认用哪条分支说明」)。硬禁止改成一句话链接:「安全闸见.claude/hooks/」。 .claude/settings.json:注册PreToolUse/ 需要的PostToolUse。.claude/hooks/*.sh:真正的匹配与exit 2。
用 git 管这三层;日常只在这台 Mac mini 上 git pull 后开 Claude Code。SSH 看终端;需要桌面批准时用 UU 远程连接。
示例一:禁止写入密钥类文件(硬门)
CLAUDE.md 里写「不要提交密钥」不够。用 PreToolUse 在写文件前拦:
# .claude/hooks/deny-secret-paths.sh
#!/bin/bash
set -euo pipefail
# stdin 为工具调用 JSON(字段名以你当前版本为准)
payload=$(cat)
path=$(printf '%s' "$payload" | jq -r '.tool_input.file_path // .tool_input.path // empty' 2>/dev/null || true)
case "$path" in
*.env|*/.env|*/.env.*|*.pem|*/id_rsa|*/.mcp.json)
echo "blocked: refuse writing secret-like path: $path" >&2
exit 2
;;
esac
exit 0
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/deny-secret-paths.sh",
"onFailure": "block"
}
]
}
]
}
}
要点:命中用 exit 2;配置里尽量加 onFailure: "block",避免脚本缺依赖时 fail-open。matcher 字符串按你版本支持的工具名调整。
示例二:危险 Bash 模式(硬门)
把「不要 git push --force 到 main」「不要 rm -rf /」从 CLAUDE.md 挪到 hook:
# .claude/hooks/deny-dangerous-bash.sh
#!/bin/bash
set -euo pipefail
payload=$(cat)
cmd=$(printf '%s' "$payload" | jq -r '.tool_input.command // empty' 2>/dev/null || true)
# 示例模式:按需增删;宁可误拦再放行,也不要漏拦
if printf '%s' "$cmd" | grep -Eiq 'git[[:space:]]+push[[:space:]]+.*(--force|-f)|rm[[:space:]]+-rf[[:space:]]+/($|[[:space:]])|curl[[:space:]]+[^|]*\|[[:space:]]*sh'; then
echo "blocked dangerous bash pattern: $cmd" >&2
exit 2
fi
exit 0
挂到 PreToolUse,matcher 对准 Bash(或你环境里的 Shell 工具名)。复杂变体比纯 deny 列表更吃 hook;这与「force-push 变体测试」那篇结论一致。
示例三:风格与命名仍留在 CLAUDE.md(软提示)
这些继续放软层即可,例如:
- 组件用 PascalCase,工具函数用 camelCase。
- 提交说明用约定前缀;默认别在正文里贴长日志。
- 改 UI 文案先搜现有词表,避免同义反复造词。
写短、可扫读。长手册拆成 Skills,需要时再加载——别把 CLAUDE.md 做成百科。软提示被 compact「记性变差」时,最多风格飘一点;硬门仍在 hooks 上。
十分钟验收(分工有没有生效)
在仓库根目录开 Claude Code(避免子目录启动漏加载项目 settings):
/hooks(或设置里等价入口)能看到刚注册的 PreToolUse。- 故意让助手「写入
.env」或「git push --force」——应被 hook 拒绝,而不是只靠口头提醒。 - 改一处命名风格相关的
CLAUDE.md句子,确认助手仍大致遵守;这证明软层还在,但你不再把安全赌在它上面。 claude --version记录版本;若刚升级,再跑你的必拦冒烟命令(见升级冒烟测试文)。tmux挂长会话;SSH 断开再连,hooks 配置仍在磁盘上,不依赖某一扇本地窗口。
为什么固定在新加坡常开 Mac mini
- 一份守卫:hooks 脚本与
settings.json只维护一份,远程用 SSH / UU 远程连接进入同一台整机。 - 长任务不断电:笔记本合盖会睡;常开机器上跑长工具循环时,硬门始终在项目目录里。
- 整机独享:项目钩子与本地密钥路径不与其他客户混用;退租抹机时配置一并结束。
- 真 macOS 工具链:你测的 Bash / jq / git 与上线环境一致。
Zumac.Ai 提供位于新加坡的实体 Mac mini M6,整机独享,支持 SSH 与 UU 远程连接,FileVault 全盘加密,退租时抹机重装。新客户整机租用 ¥999/月起,季付 ¥2,697.30,半年付 6 送 1 为 ¥5,994,年付 12 送 2 为 ¥11,988;已购买客户续费保持原价。新客户首单自审核开通当天起租,续费及老客户新订单自付款当天起租。建议先租一个月,把「CLAUDE.md 变短 + hooks 变硬」在同一台机器上跑通。实际延迟取决于你本地的网络线路。
小结
CLAUDE.md是软提示,适合风格与约定;长循环和 compact 后可能漏执行硬禁止。- 必须每次生效的规则放
PreToolUsehooks:exit 2/ JSON deny,安全类加onFailure: "block"。 - 会话中改过
CLAUDE.md时,核对 Claude Code 版本,避免旧 compact 逻辑继续用旧副本;硬规则仍以 hooks 为准。 - 把项目 hooks 固定在新加坡常开 Mac mini,用 git 统一,SSH 或 UU 远程连接日常接入。
