Claude Code 自 v2.1.178 起对项目内子代理目录引入”离工作目录最近者胜出”的同名冲突解决策略:嵌套扫描继续按目录树上溯,但当多个 .claude/agents/ 定义了同一个 name 时,当前工作目录最近的那一份生效,取代旧版”按文件系统读取顺序二选一”的隐式行为。这一改动对 monorepo 与多包仓库的协作具有实质意义——团队可以为同一个角色维护多个版本(通用版 vs 包级特化版),并通过”在哪个目录下启动 Claude”来隐式选择生效版本,无需引入新的配置开关。
一、嵌套扫描机制的来龙去脉
官方文档描述项目内子代理目录的发现方式是”从当前工作目录向上回溯,扫描沿途每一个 .claude/agents/“。这一规则在 v2.1.178 之前就存在,工程师可以在 monorepo 根放通用版 reviewer、在某个包目录放特化版 reviewer。v2.1.178 之前,同名冲突的解决方式是不确定的”按文件系统读取顺序选一个”,结果常常令人困惑。
自 v2.1.178 起,规则被显式化为”最近者胜出”:当存在多个 .claude/agents/ 定义同一 name 时,目录树中离当前工作目录最近的那一份会被加载,其他同名文件被忽略。这条规则同样适用于嵌套的 workflows 和 output-styles,构成统一的”近者优先”语义。
理解这一变化,要分清两种”同名”:同一 .claude/agents/ 子树下的同名仍由文件系统读取顺序决定、不保证确定性;不同深度的 .claude/agents/ 之间的同名则按”近者优先”——这才是 v2.1.178 的关键。
二、对团队协作的四个实际意义
“最近者胜出”看似只是把隐式规则显式化,实际上改写了团队演进 subagent 的协作边界。
| 协作场景 | 旧版痛点 | v2.1.178 及以上的解法 |
|---|---|---|
| monorepo 包级特化 reviewer | 同名冲突导致”开盲盒” | 在包目录放特化版、仓库根放通用版,靠启动位置决定 |
| 灰度发布新 prompt | 需临时改文件才能切换 | 让新版本落在子目录、提示”在该子目录运行”即可 |
| 团队成员维护不同偏好 | 用 --add-dir 临时引入 |
借助嵌套扫描天然按 CWD 切换,无需临时目录 |
| 历史回滚 | 改文件破坏 git 历史 | 把候选版本都提交到不同目录,运行时切到对应目录即可 |
注意第一条:同一目录下的两个同名文件仍然没有可记录的优先级,文件系统读到的顺序决定谁生效——这意味着”在同一目录里放两个同名 .md”是反模式,请用子目录做隔离。
三、典型目录结构与行为示例
下面是一个 monorepo 落地”最近者胜出”的标准布局,仓库根放通用版 reviewer、packages/web/.claude/agents/ 放前端特化版、packages/api/.claude/agents/ 放后端特化版。
repo-root/
├── .claude/
│ └── agents/
│ └── code-reviewer.md ← 通用版,覆盖所有目录
└── packages/
├── web/
│ └── .claude/
│ └── agents/
│ └── code-reviewer.md ← 前端特化版,在 web/ 下启动时生效
└── api/
└── .claude/
└── agents/
└── code-reviewer.md ← 后端特化版,在 api/ 下启动时生效
当工程师在 packages/web/ 下启动 Claude Code 时,扫描器向上回溯,会依次发现 packages/web/.claude/agents/ 与 repo-root/.claude/agents/,两份都定义了 code-reviewer。按”最近者胜出”,加载 packages/web/ 这一份,前端规范生效;当他切到 packages/api/,同名时加载后端特化版。无需任何额外配置、临时目录或命名空间前缀。
把”切换版本”建模成”切换工作目录”,是这条规则在工程体验上的最大红利。
四、版本演进的工程化流程
把”在哪个目录启动”当作版本开关,团队就能在不引入新配置文件的前提下做灰度与回滚。下面给出推荐的四步流程。
- 在仓库根的
.claude/agents/维护一份稳定版,作为兜底; - 当需要灰度新 prompt 时,在目标子目录(例如
packages/web/.claude/agents/)新增同名文件,覆盖通用版; - 在 PR 中显式标注”在该子目录启动即生效”,让评审者能复现;
- 灰度通过后,把特化版内容回写至根目录或保留子目录布局长期并存,让”在 web 目录跑测试”成为团队的隐式约定。
这套流程对历史回滚也成立——只需把 git 切回旧版本,工作目录不变即可加载旧规则,避免”改了文件又怕破坏协作”的纠结。
五、与 subagent 前缀命名空间的差异
“最近者胜出”针对的是项目级子代理(项目内的子代理目录这一层)。插件作用域下的子代理则使用冒号分隔的命名空间(my-plugin:review:security),子目录路径会变成 scoped identifier 的一部分——两套机制互不冲突,但容易混淆。
| 维度 | 项目级 .claude/agents/ |
插件 agents/ 目录 |
|---|---|---|
| 命名空间 | 仅靠 name 字段识别,路径不参与 | 子目录路径参与,如 my-plugin:review:security |
| 同名冲突解决 | 离工作目录近者胜出 | 插件名优先,路径不冲突 |
| 共享范围 | 仓库内共享 | 跨项目共享,靠插件分发 |
| 适用场景 | monorepo、灰度、回滚 | 跨团队工具链打包 |
理解这两套机制后,团队在选择”项目内子代理目录嵌套”还是”插件打包”时会更清晰:只在仓库内部做版本演进与灰度,嵌套扫描是首选;要跨仓库分发,迁到插件。
六、排障与诊断
/doctor 设置检查会报告同一目录内共享 name 的文件,并建议重命名或删除。在 v2.1.205 之前,/doctor 还会打开诊断屏幕,列出重复项并显示哪个定义处于活跃状态——这条历史信息对追查”为什么我的特化版没生效”特别有用。
# /doctor 输出片段(示意)
- packages/web/.claude/agents/code-reviewer.md ← 活跃(最近者胜出)
- .claude/agents/code-reviewer.md ← 被忽略
如果发现”特化版没生效”,优先检查启动时的工作目录是否真的落在特化版所在子目录;其次用 /memory 列出当前已加载的 agent 列表确认;最后用 /doctor 查看是否存在同名冲突。
到这里,”项目内子代理目录 + 嵌套扫描 + 最近者胜出”作为一套组合设计,就从”新机制”沉淀成了团队可以直接套用的协作模式。
常见问题(FAQ)
Q1:同一目录下两个同名文件谁生效?
按文件系统读取顺序,行为不可预测,建议用子目录隔离而不是同名覆盖。
Q2:怎么确认特化版正在生效?
运行 /memory 查看已加载 agent,再用 /doctor 列出冲突与活跃定义。
Q3:插件和项目级子代理能否同名?
可以,插件名通过冒号命名空间隔离,不会与项目级 name 冲突。