OpenCode 会加载子目录的 CLAUDE.md 吗
- 启动时发生了什么
- 全局配置也会加载
- 真正有意思的是运行时
- 这个功能什么时候加的
- Claude Code 的做法
- Codex 的做法
- Hermes Agent 的做法
- 四端对比
- 什么时候该放子目录 AGENTS.md
- 验证方法
- 写在最后
Monorepo(单一代码仓库,多个项目共用一个 Git 仓库)里写了分层的 AGENTS.md,每个子目录一套规范,Agent 应该按需加载——想得很美。但你有没有验证过,Agent 真的读到了你放的那些文件?
我之前以为得同时维护 CLAUDE.md 和 AGENTS.md,后来发现只放一个就行。

启动时发生了什么
OpenCode 启动时做了一件事:从当前目录向上遍历,按 AGENTS.md → CLAUDE.md → CONTEXT.md 的顺序查找指令文件。
// [instruction.ts](https://github.com/anomalyco/opencode/blob/dev/packages/opencode/src/session/instruction.ts) - systemPaths()
for (const file of instructionFiles) {
const matches = yield* fs.findUp(file, ctx.directory, ctx.worktree)
if (matches.length > 0) {
matches.forEach((item) => paths.add(path.resolve(item)))
break // 第一个匹配就停
}
}
instructionFiles 的顺序是 ["AGENTS.md", "CLAUDE.md", "CONTEXT.md"]。所以如果你项目根目录同时有 AGENTS.md 和 CLAUDE.md,只读 AGENTS.md。
这里要插一句背景:CLAUDE.md 是 Claude Code 的默认规则文件,相当于 OpenCode 的 AGENTS.md。OpenCode 为了兼容 Claude Code 用户,把 CLAUDE.md 作为 fallback 加载——没有 AGENTS.md 时就读 CLAUDE.md。
启动阶段只加载根目录这一层。子目录的 AGENTS.md?启动时不管。
全局配置也会加载
除了项目级文件,OpenCode 启动时还会加载全局配置目录的指令文件:
// instruction.ts - systemPaths()
const globalFiles = [
path.join(global.config, "AGENTS.md"), // ~/.config/opencode/AGENTS.md
...(!flags.disableClaudeCodePrompt
? [path.join(global.home, ".claude", "CLAUDE.md")] // ~/.claude/CLAUDE.md
: []),
]
遍历顺序:先找 ~/.config/opencode/AGENTS.md,存在就读它;不存在则 fallback 到 ~/.claude/CLAUDE.md。disableClaudeCodePrompt 默认 false,所以 ~/.claude/CLAUDE.md 默认会被读取——这是 OpenCode 的 Claude Code 兼容层,给迁移用户用的。
注意:~/.config/opencode/CLAUDE.md 不在加载列表里。全局只认两个位置,AGENTS.md 优先。
所以启动时完整的加载链路是:
- 全局:
~/.config/opencode/AGENTS.md→~/.claude/CLAUDE.md(第一个存在就停) - 项目:从 CWD 向上 findUp,按
AGENTS.md→CLAUDE.md→CONTEXT.md顺序,第一个匹配就停 - opencode.json 里的
instructions字段指定的额外文件
三层叠加,全部注入 system prompt。
真正有意思的是运行时
当你用 OpenCode 读取一个文件时,比如 packages/backend/routes/auth.ts,resolve() 函数干了这件事:
// [instruction.ts](https://github.com/anomalyco/opencode/blob/dev/packages/opencode/src/session/instruction.ts) - resolve()
const target = path.resolve(filepath)
let current = path.dirname(target)
// 从文件所在目录向上遍历,直到项目根目录
while (current.startsWith(root) && current !== root) {
const found = yield* find(current) // 在当前目录找 AGENTS.md / CLAUDE.md
if (!found || found === target || sys.has(found) || already.has(found)) {
current = path.dirname(current)
continue
}
// 找到了就读取内容,附加到 tool output
results.push({ filepath: found, content: `Instructions from: ${found}\n${content}` })
current = path.dirname(current)
}
翻译成人话:
- 从
packages/backend/routes/开始找——先看有没有 AGENTS.md,没有就看 CLAUDE.md - 往上走到
packages/backend/,同样的逻辑 - 继续往上,直到项目根目录(根目录的已经在 systemPaths 里了,跳过)
- 每层目录只加载第一个匹配的文件(AGENTS.md 优先于 CLAUDE.md),同层不会两个都读
- 跨目录层级则是叠加——
routes/AGENTS.md和backend/AGENTS.md都会被读到 - 同一条消息内不会重复加载同一个文件
find() 函数内部遍历的是 instructionFiles 列表,返回每层目录的第一个存在的文件。所以同层有 AGENTS.md 和 CLAUDE.md 两个文件时,只读 AGENTS.md——跟启动时的逻辑一样,first match wins。
也就是说,OpenCode 在你读写子目录文件时,会自动加载沿途所有子目录的 AGENTS.md 和 CLAUDE.md。
这个功能什么时候加的
不是一直有的。翻 anomalyco/opencode 的 git log 可以看到完整时间线:
| 日期 | 事件 |
|---|---|
| 2025-12-28 | #6316 提出:请求子目录 AGENTS.md 自动发现(Context Auto-Discovery) |
| 2026-01-10 | #7576 提出:请求基于文件上下文自动选择嵌套 AGENTS.md |
| 2026-01-26 | PR #10678 合入:feat: dynamically resolve AGENTS.md files from subdirectories as agent explores them |
| 2026-01-28 | Commit 5585907 修复:并行工具调用不会重复加载 AGENTS.md |
| 2026-02-01 | 修复 #11581:读取指令文件时防止重复注入 |
| 2026-02-04 | 修复 #11536:优先使用 OPENCODE_CONFIG_DIR 查找 AGENTS.md |
功能由 Aiden Cline (@rekram1-node) 实现,从 issue 提出到合入不到一个月。两个 issue 都已关闭。后续一周内连续修了三个 bug——并行加载重复、指令文件重复注入、配置目录优先级——说明这个功能上线后踩了不少坑。
如果你用的 OpenCode 版本早于 2026 年 1 月底,子目录 AGENTS.md 是不会被自动加载的。
Claude Code 的做法
Claude Code 的行为几乎一样:启动时加载根目录 CLAUDE.md,运行时按需懒加载子目录 CLAUDE.md(详见 官方文档)。
区别在细节:
| 维度 | OpenCode | Claude Code |
|---|---|---|
| 启动加载 | findUp 向上遍历,AGENTS.md 优先 |
向上遍历,CLAUDE.md |
| 运行时加载 | resolve() 从文件目录向上遍历 |
按需懒加载子目录 |
| 文件名优先级 | AGENTS.md > CLAUDE.md | 只认 CLAUDE.md |
| 加载位置 | 附加到 tool output metadata | 附加到 context |
| 去重 | claims Map + loaded metadata |
类似机制 |
核心差异在文件名优先级。OpenCode 同时有 AGENTS.md 和 CLAUDE.md 时只读 AGENTS.md;Claude Code 只认 CLAUDE.md。所以你想两端通用,只放 CLAUDE.md 就够了——OpenCode 会 fallback 读取。
Codex 的做法
Codex(OpenAI)走了第三条路。它只认 AGENTS.md,不认 CLAUDE.md(相关讨论见 openai/codex#9836)。
启动时先向上找 project root(由 project_root_markers 判定,默认含 .git 等标记),再从 root 向下遍历到 CWD,每层目录按 AGENTS.override.md → AGENTS.md → 自定义 fallback 文件名的顺序查找。找到就加载,最多每层一个文件。
# ~/.codex/config.toml — 可以配置 fallback 文件名和大小上限
project_doc_fallback_filenames = ["TEAM_GUIDE.md", ".agents.md"]
project_doc_max_bytes = 65536
合并规则:从根往下把沿途各层的文件拼接(concatenate)起来(源码里用 --- project-doc --- 分隔),靠近 CWD 的排在后面——是叠加,不是覆盖。全局配置在 ~/.codex/AGENTS.md。
关键区别:Codex 只在启动时做一次遍历,运行时不会动态发现子目录的 AGENTS.md。 你在 services/payments/ 放了 AGENTS.md,但从项目根启动 Codex 时如果没走到那个目录,就读不到。
Hermes Agent 的做法
Hermes 走了第四条路。它支持 AGENTS.md、CLAUDE.md,还多了一个 .hermes.md。
启动时只看 CWD,按优先级加载一个:
# prompt_builder.py — build_context_files_prompt()
project_context = (
_load_hermes_md(cwd_path) # .hermes.md / HERMES.md(向上遍历到 git root)
or _load_agents_md(cwd_path) # AGENTS.md / agents.md(CWD only)
or _load_claude_md(cwd_path) # CLAUDE.md / claude.md(CWD only)
or _load_cursorrules(cwd_path) # .cursorrules / .cursor/rules/*.mdc(CWD only)
)
第一个找到就停。.hermes.md 会向上遍历到 git root,但 AGENTS.md 和 CLAUDE.md 只看 CWD。
运行时:SubdirectoryHintTracker(源码注释写明思路源自 Block 的 goose)在 agent 通过工具访问子目录文件时,自动发现沿途的上下文文件:
# subdirectory_hints.py
_HINT_FILENAMES = ["AGENTS.md", "agents.md", "CLAUDE.md", "claude.md", ".cursorrules"]
从文件所在目录向上遍历,每层目录按优先级找第一个匹配,最多走 5 层,限制在工作目录树内。发现的 hint 附加到 tool result,不是 system prompt。
跟 OpenCode 的 resolve() 思路几乎一样——沿途各层子目录都会叠加加载,同层也是 first match wins。真正的区别有两个:
- 限制在工作目录树内,不会加载
~/.codex/AGENTS.md或~/.claude/CLAUDE.md,避免跨 agent 的指令串味 - 最多向上走 5 层,防止深层路径一路扫到文件系统根目录
四端对比
| 维度 | OpenCode | Claude Code | Codex | Hermes Agent |
|---|---|---|---|---|
| 默认规则文件 | AGENTS.md(fallback CLAUDE.md) | CLAUDE.md | AGENTS.md | .hermes.md > AGENTS.md > CLAUDE.md > .cursorrules |
| 全局配置 | ~/.config/opencode/AGENTS.md |
~/.claude/CLAUDE.md |
~/.codex/AGENTS.md |
~/.hermes/SOUL.md(独立身份,非规则文件) |
| 启动加载方向 | CWD 向上 findUp | CWD 向上 findUp | project root 向下到 CWD | CWD(仅 .hermes.md 向上到 git root) |
| 运行时加载 | ✅ 从文件目录向上遍历 | ✅ 按需懒加载子目录 | ❌ 只启动时一次 | ✅ 从文件目录向上遍历(最多 5 层) |
| 每层加载数量 | 一个(first match wins) | 叠加(多文件拼接) | 最多一个 | 一个(first match wins) |
| 加载位置 | tool output metadata | context | system prompt | tool result |
| 支持 override 文件 | ❌ | ❌ | ✅ AGENTS.override.md |
❌ |
| 安全边界 | 无显式限制 | 类似 | 无显式限制 | ✅ 限制在工作目录树内 |
四种工具,四种哲学:OpenCode 每层取一个但跨层叠加,Claude Code 按需加载全部,Hermes 每层只取一个且限制层数,Codex 启动时把 root→CWD 路径上的文件一次性拼好(路径之外不管)。
什么时候该放子目录 AGENTS.md
值得放的场景:
- Monorepo 里前端/后端/移动端规范差异大
- 某个模块有特殊约定(比如测试目录的 UserFactory 模式、API 目录的命名规则)
- 根目录 AGENTS.md 已经超过 200 行,需要拆分
不需要放的场景:
- 项目小,一个根目录文件覆盖得了
- 子目录之间规范差异不大
- 你维护了 CLAUDE.md 但没维护 AGENTS.md——OpenCode 会 fallback,但只有根目录那层会 fallback,子目录的 CLAUDE.md 也会被加载
一个容易踩的坑: 你在 src/ 放了 AGENTS.md,在 src/utils/ 也放了。读 src/utils/file.ts 时,两个都会被加载。如果内容有冲突,后加载的不会覆盖先加载的——它们是叠加关系,不是覆盖。
验证方法
想知道你的 AGENTS.md 到底有没有被读到?最简单的方式:读一个子目录文件,看 OpenCode 返回的 metadata 里有没有 loaded 字段。
或者在 AGENTS.md 里写一条明显指令,比如"每次读这个目录的文件时,在回复开头加 [LOADED]"。如果 Agent 照做了,说明加载成功。
opencode 会有类似 Loaded your-path/CLAUDE.md的输出。
写在最后
Agent 的指令文件不是"写了就生效"的魔法。它有明确的加载时机、遍历规则和去重机制。了解这些,你才能把 AGENTS.md 放对位置、写对内容。
别猜,读源码。
本文链接:OpenCode 会加载子目录的 CLAUDE.md 吗 - https://h89.cn/archives/627.html
版权声明:原创文章 遵循 CC 4.0 BY-SA 版权协议,转载请附上原文链接和本声明。