上下文工程 · 09 · CLAUDE.md 与项目级配置

0 阅读10分钟

系列第 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.mdsettings.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.mdLLM 决策性输出
"禁止 rm -rf"settings.json deny强制执行
"每次提交跑 lint"settings.json hook自动触发
"默认用 opus 模型"settings.json modelharness 配置
"我个人偏好简洁注释"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 影响也不同:

配置进入 promptcache 影响
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 平台:

  1. 明确分层:项目指令(自然语言)、行为配置(结构化)、工件存储(文件)—— 三层不能混
  2. 嵌套支持:用户级、项目级、子目录级、临时(worktree)级
  3. 覆盖语义清晰:CSS 式级联,最具体的赢
  4. 配置改动的副作用文档化:哪些会让 cache 失效、哪些立即生效
  5. 专门接口改配置:不要让 agent 裸编辑配置文件
  6. gitignore 模板:默认提供,避免新用户犯错
  7. schema 验证:settings.json 启动时校验,错配早报错
  8. 审计 settings.local.json:团队层面要能 review 个人覆盖
  9. CLAUDE.md 长度告警:超过阈值提醒拆分
  10. hook 化优先:能自动化的不要靠 agent 自律

11. 一句话总结

CLAUDE.md 是"项目说给 LLM 听的话",settings.json 是"项目对 harness 立的规矩",.claude/ 是"工作台抽屉"。三者职责不同、生命周期不同、对 cache 影响不同。把每条配置放对位置,agent 在项目里才既灵活又有边界。

下一篇:10 · 错误恢复与上下文修复