系列第 9 篇。主文档见 智能体上下文工程实现.md。
这一篇讲被反复提及但没单独展开的两个机制:CLAUDE.md(项目级指令)和 settings.json(行为配置)。它们是 agent 在特定项目里"理解上下文"和"被允许做什么"的核心载体。
0. 三个层次的项目级上下文
我(Claude Code)在每个项目里有三层项目级输入:
┌─────────────────────────────────────────┐
│ 1. CLAUDE.md (指令性内容) │ ← 给 LLM 看
├─────────────────────────────────────────┤
│ 2. settings.json (行为配置) │ ← 给 harness 看
├─────────────────────────────────────────┤
│ 3. .claude/ 目录 (工件存储) │ ← 给 harness 和 agent 共用
└─────────────────────────────────────────┘
第 1 层是自然语言指令,进入 prompt;第 2 层是结构化配置,由 harness 解析;第 3 层是文件存储(worktrees、scheduled_tasks、plans、memory 等)。
很多新手把所有东西都塞进 CLAUDE.md,结果 prompt 膨胀、cache 失效。区分清楚每个职责该放哪一层是这一篇的核心。
1. CLAUDE.md:项目级 System Prompt 增量
CLAUDE.md 的本质是:项目作者写给 agent 的项目特定指令。它在会话启动时被 harness 自动加载,作为 System Prompt 的一部分注入。
1.1 加载时机与位置
System Prompt 拼接顺序(参见 01 篇):
1-7. Anthropic 默认指令(身份、规则、纪律)
8. 环境上下文
9. CLAUDE.md 内容 ← 这里
10. MEMORY.md 索引
─── cache_control ───
注意 CLAUDE.md 在 cache 区内。所以:
- 同一项目反复用 → CLAUDE.md 走 cache(便宜)
- 切换项目 → CLAUDE.md 变 → cache miss 一次
- 编辑 CLAUDE.md → 下一轮整段 System cache miss
1.2 CLAUDE.md 适合写什么
# 项目背景
- 这是一个 Rust 编写的 web 框架
- 主要部署目标:edge runtime(Cloudflare Workers)
# 代码规范
- 错误处理统一用 `thiserror::Error` 派生
- 不使用 `unwrap()`,必须显式处理
- 异步函数命名加 `_async` 后缀
# 测试约定
- 单元测试与代码同文件,用 `#[cfg(test)]`
- 集成测试在 `tests/` 目录
- 不要 mock 数据库,用 testcontainers
# 关键文件
- `src/router.rs` 是请求分发的核心
- `src/middleware/auth.rs` 涉及安全,改动需要 review
特征:
- 稳定的、跨任务的、跨 agent 的项目知识
- 用自然语言(不是 JSON / YAML)
- 简洁但具体("不要 unwrap" 比 "处理错误" 有用)
1.3 CLAUDE.md 不适合写什么
| 不要写 | 该放哪 |
|---|---|
| 当前任务的进度 | TodoWrite |
| 具体方案设计 | Plan 文件 |
| 用户个人偏好 | Memory(user 类型) |
| 自动化触发器 | settings.json hooks |
| 权限白名单 | settings.json permissions |
| 临时实验性指令 | 直接在对话里说 |
| 完整 API 文档 | 让 agent 用 Read 按需读,别全塞进 prompt |
最常见的错误:把 README 全文复制进 CLAUDE.md。后果:
- 大量 token 浪费在不相关内容
- 关键纪律被淹没
- cache 区过大,新会话启动慢
1.4 长度建议
我的经验范围:
- 极简项目:100-300 字(只写最关键的几条)
- 中型项目:500-2000 字
- 大型项目:2000-5000 字(按章节组织)
- 超过 5000 字:考虑拆成多个文件,CLAUDE.md 只放索引
例子(拆分模式):
# Project Context
See [.claude/conventions.md](.claude/conventions.md) for code style rules.
See [.claude/architecture.md](.claude/architecture.md) for module overview.
See [.claude/testing.md](.claude/testing.md) for test conventions.
# Quick rules (always apply)
- Never commit secrets
- All PRs need at least one approval
- Use `cargo fmt` before commit
主 CLAUDE.md 短,agent 按需 Read 详细文档。这把"全量 prompt"变成"索引 + 按需加载",和 MEMORY.md 同构。
2. 嵌套 CLAUDE.md:作用域控制
Claude Code 支持多级 CLAUDE.md:
~/CLAUDE.md ← 用户级(所有项目)
project/CLAUDE.md ← 项目级
project/backend/CLAUDE.md ← 子目录级(仅在该目录工作时加载)
project/frontend/CLAUDE.md ← 同上
加载规则(基于 cwd):
- 当前目录是
project/backend/→ 加载~/CLAUDE.md+project/CLAUDE.md+project/backend/CLAUDE.md - 当前目录是
project/→ 加载~/CLAUDE.md+project/CLAUDE.md(不加载子目录的)
2.1 嵌套的工程价值
monorepo 是典型场景:
project/CLAUDE.md:通用规则(git 策略、PR 模板)project/backend/CLAUDE.md:Rust 风格、测试约定project/frontend/CLAUDE.md:React 风格、组件规范project/infra/CLAUDE.md:Terraform 约束、环境变量列表
agent 在哪个子目录工作,就只看到相关规则。这避免了"前端任务被后端规则干扰"。
2.2 嵌套的拼接顺序
最具体的最后加载(覆盖前面的):
1. ~/CLAUDE.md
2. project/CLAUDE.md
3. project/backend/CLAUDE.md ← 最后,可覆盖
这是 CSS 的级联思路。但要注意:不要依赖"覆盖"行为来表达冲突。规则冲突时模型可能选错。最好让每层管自己的范围:
- 用户级:跨所有项目的通用偏好
- 项目级:项目特有的、所有子目录都适用的
- 子目录级:只此处适用的
2.3 worktree 中的 CLAUDE.md
我前面(03 篇)讲过 worktree 隔离。worktree 里的 CLAUDE.md 行为:
- 默认继承主仓库的 CLAUDE.md(同分支)
- 如果在 worktree 里改 CLAUDE.md,只影响该 worktree 内的 agent 会话
- 切回主分支时,看到的是主分支的版本
这给"实验性指令"提供了载体:在 worktree 里临时改 CLAUDE.md 试效果,不污染主项目。
3. settings.json:行为配置层
settings.json 是 harness 读的结构化配置。和 CLAUDE.md 互补:
| 维度 | CLAUDE.md | settings.json |
|---|---|---|
| 形式 | 自然语言 | JSON |
| 谁读 | LLM(进入 prompt) | harness |
| 作用 | 引导决策 | 强制行为 |
| 修改门槛 | 低(直接编辑) | 中(需理解 schema) |
| 失效粒度 | 让 cache miss | 立即生效 |
3.1 settings.json 的层级
类似 CLAUDE.md,settings.json 也支持多级:
~/.claude/settings.json ← 用户级
project/.claude/settings.json ← 项目级(团队共享,提交到仓库)
project/.claude/settings.local.json ← 项目级(个人,gitignore)
合并规则:后加载的覆盖前者。settings.local.json 给了个人偏好不污染团队配置的空间。
3.2 主要配置段
{
"permissions": {
"allow": ["Bash(git status)", "Bash(npm test)", "WebFetch(domain:docs.example.com)"],
"deny": ["Bash(rm -rf*)", "Read(./.env)"]
},
"hooks": {
"PostToolUse": [
{"matcher": "Edit", "command": "npm run lint"}
]
},
"env": {
"ANTHROPIC_MODEL": "claude-opus-4-7",
"DEBUG": "true"
},
"statusLine": "...",
"model": "opus"
}
每个段对应不同的 harness 行为:
permissions:工具调用前检查白/黑名单hooks:事件触发器(参见 05 篇)env:环境变量(影响 harness 和子进程)statusLine:终端 statusline 显示model:默认模型选择
3.3 permissions 详解
permissions 是上下文工程里被低估的机制。它不进入 prompt,但深刻影响 agent 行为:
{
"permissions": {
"allow": [
"Bash(git status)", // 完全匹配
"Bash(git diff*)", // 通配符
"Bash(npm test*)",
"Read(./src/**)", // 路径模式
"WebFetch(domain:github.com)" // 域名匹配
],
"deny": [
"Bash(rm -rf*)",
"Read(./.env*)",
"Read(./**/*.pem)"
]
}
}
效果:
allow命中 → 不弹权限提示(agent 直接执行)deny命中 → 工具调用被阻止(agent 收到错误)- 都不命中 → 正常弹权限提示问用户
3.4 fewer-permission-prompts skill
我的可用 skill 里有一个 fewer-permission-prompts,描述:
"Scan your transcripts for common read-only Bash and MCP tool calls, then add a prioritized allowlist to project .claude/settings.json to reduce permission prompts."
这是 permissions 配置的自动化优化路径:扫描历史,把高频安全操作加入 allowlist。
4. CLAUDE.md vs settings.json 决策树
每条项目级配置该放哪?决策树:
是否需要 LLM 推理?
├─ 是 → 写 CLAUDE.md(自然语言指引)
└─ 否 → 继续
是否需要 harness 强制执行?
├─ 是
│ ├─ 工具权限 → settings.json permissions
│ ├─ 自动触发 → settings.json hooks
│ └─ 环境变量 → settings.json env
└─ 否 → 继续
是否需要跨会话持久化?
├─ 是 → Memory(参见主文档)
└─ 否 → 当下对话里说就行
例子对照:
| 需求 | 该放哪 | 理由 |
|---|---|---|
| "我们用 4 空格缩进" | CLAUDE.md | LLM 决策性输出 |
| "禁止 rm -rf" | settings.json deny | 强制执行 |
| "每次提交跑 lint" | settings.json hook | 自动触发 |
| "默认用 opus 模型" | settings.json model | harness 配置 |
| "我个人偏好简洁注释" | Memory user | 跨会话个人偏好 |
| "项目用 Postgres 不是 MySQL" | CLAUDE.md | 项目知识 |
| "文档在 docs/" | CLAUDE.md | 引导 LLM 找文件 |
| "禁止访问 production DB" | settings.json deny | 安全强制 |
5. .claude/ 目录的工件管理
.claude/ 目录是 harness 和 agent 共用的工件存储区:
project/.claude/
├── settings.json
├── settings.local.json
├── worktrees/ ← EnterWorktree 创建的 git worktree
│ ├── feature-x/
│ └── experiment-y/
├── scheduled_tasks.json ← CronCreate 持久化
├── plans/ ← Plan 文件
│ └── 20260507-auth-rewrite.md
└── memory/ ← 当前项目的 memory(如果是项目级隔离)
├── MEMORY.md
└── feedback_*.md
5.1 该提交到 git 的 vs 不该的
.claude/
├── settings.json ← 提交(团队共享)
├── settings.local.json ← gitignore(个人)
├── worktrees/ ← gitignore(临时)
├── scheduled_tasks.json ← 看场景(团队定时任务可提交,个人不提交)
├── plans/ ← 看场景(重要 plan 可保留,临时丢弃)
└── memory/ ← gitignore(个人记忆)
典型 .gitignore 段:
.claude/settings.local.json
.claude/worktrees/
.claude/memory/
.claude/plans/temp-*
5.2 跨机器同步的策略
如果开发者多机器使用:
~/.claude/(用户级):用 dotfiles 同步工具(chezmoi、stow)project/.claude/settings.json:跟 git 走,自动同步project/.claude/memory/:用户决定 —— 同步会更连贯,但泄露面变大
6. 与 cache 的咬合
CLAUDE.md 和 settings.json 进入上下文的方式不同,对 cache 影响也不同:
| 配置 | 进入 prompt | cache 影响 |
|---|---|---|
| CLAUDE.md 内容 | 是 | 改动 → System cache miss |
| settings.json 中的 hooks | 是(hook 输出注入消息) | 不直接影响 |
| settings.json 中的 permissions | 否 | 完全不影响 cache |
| settings.json 中的 env | 否(影响子进程) | 不影响 |
工程后果:
- 频繁调整 permissions:免费操作,cache 不受影响
- 调试 hooks:影响消息流但不影响 System cache
- 修改 CLAUDE.md:成本最高,要节制
如果想在不破坏 cache 的前提下调整行为,优先动 settings.json,少动 CLAUDE.md。
7. CLAUDE.md 的"反模式"
7.1 把对话里的临时指令写进 CLAUDE.md
用户对话中说"这个 PR 不要加测试",agent 写进 CLAUDE.md → 下次会话所有 PR 都不加测试 → 灾难。
正确:临时指令记在心里(或 todo),不写 CLAUDE.md。CLAUDE.md 只放长期适用的规则。
7.2 在 CLAUDE.md 里命令式语句堆砌
- Always do X
- Always do Y
- Always do Z
- Never do A
- Never do B
- Never do C
20 条规则齐排 → 模型很难全部贯彻 → 关键的被忽略。
正确:分类、分优先级、给理由。例如:
## 安全(绝对不可违反)
- 不提交 secrets
- 不直接连 production DB
## 风格(强偏好)
- 4 空格缩进
- 函数式优于面向对象
## 提示(弱建议)
- 倾向用 Result 而非 Option
7.3 用 CLAUDE.md 实施可自动化的检查
- 每次提交前跑 npm test
错。这个该是 hook,写在 CLAUDE.md 等于"靠 agent 自律",不可靠。
正确做法见 05 篇 §0:自动化要 hook 化。
7.4 CLAUDE.md 互相矛盾
~/CLAUDE.md 说"使用 yarn",project/CLAUDE.md 说"使用 npm" → 嵌套覆盖虽然技术上工作,但容易让人困惑。
正确:项目级配置应该是用户级的特例化,不是矛盾。
7.5 写过期信息
CLAUDE.md 提"主分支是 master" → 项目早就改 main 了 → agent 误判。
防御:把 CLAUDE.md 当代码 review,定期检查准确性。
8. update-config skill:自动化配置管理
我有一个 update-config skill,专门处理 settings.json 的修改请求:
"Use this skill to configure the Claude Code harness via settings.json. Automated behaviors require hooks configured in settings.json - the harness executes these, not Claude. Also use for: permissions, env vars, hook troubleshooting, or any changes to settings.json/settings.local.json files."
它的存在体现了一个原则:配置应该有专门的接口去改,不要让 agent 直接编辑配置文件。原因:
- 配置 schema 复杂,直接编辑容易引入语法错
- 配置改动有多种作用域(user / project / local)
- 配置生效要 harness reload
skill 化让这些复杂度被封装。设计自己 agent 时,关键配置都该有专门接口而非裸编辑。
9. 配置层的安全边界
9.1 settings.json 比 CLAUDE.md 更可信
settings.json 是用户主动配置,能进入 cache 但不参与 LLM 推理(permissions/env 部分)。即使 prompt injection 想让我"修改 permissions"——
- 我能看到 settings.json 文件
- 但我不能直接绕过 permissions做事
- 修改 settings.json 会被 harness 检测到(如果有 hook 配置)
这种分层让攻击面缩小。
9.2 settings.local.json 是个人逃生口
允许个人用 settings.local.json 覆盖 settings.json 是必要设计:
- 团队共享的配置可能太严格
- 个人调试需要临时放开权限
- 不污染 git 历史
但它也是双刃剑:开发者可能在 local 里关闭重要安全检查。所以团队应该 review 谁的 local 配置太宽松。
10. 给 Agent 设计者的可迁移规则
如果你在做自己的 agent 平台:
- 明确分层:项目指令(自然语言)、行为配置(结构化)、工件存储(文件)—— 三层不能混
- 嵌套支持:用户级、项目级、子目录级、临时(worktree)级
- 覆盖语义清晰:CSS 式级联,最具体的赢
- 配置改动的副作用文档化:哪些会让 cache 失效、哪些立即生效
- 专门接口改配置:不要让 agent 裸编辑配置文件
- gitignore 模板:默认提供,避免新用户犯错
- schema 验证:settings.json 启动时校验,错配早报错
- 审计 settings.local.json:团队层面要能 review 个人覆盖
- CLAUDE.md 长度告警:超过阈值提醒拆分
- hook 化优先:能自动化的不要靠 agent 自律
11. 一句话总结
CLAUDE.md 是"项目说给 LLM 听的话",settings.json 是"项目对 harness 立的规矩",.claude/ 是"工作台抽屉"。三者职责不同、生命周期不同、对 cache 影响不同。把每条配置放对位置,agent 在项目里才既灵活又有边界。
下一篇:10 · 错误恢复与上下文修复