Claude Code 自动化与排错
Claude Code 自动化与排错
单体仓库与大型代码库
6 分钟阅读
在单体仓库或大型代码库中搭建 Claude Code
为单体仓库和大型单一代码库配置 Claude Code,使用嵌套的 CLAUDE.md 文件、稀疏 worktree、代码智能和按目录划分的技能,让 Claude 始终专注于你正在处理的代码。
一个大型代码库可以是一个拥有数百万行代码的仓库,也可以是一个拥有众多软件包的单体仓库。Claude Code 在任何规模下都能工作,但随着代码库增长,那些为较小项目调优的默认设置,可能会用与当前任务无关的指令和文件读取填满上下文窗口,消耗 Token 并降低 Claude 的表现。
本指南向个人开发者和工程团队展示如何将 Claude 的范围限定到一项任务所涉及的代码库部分。每一节都会说明某项设置是你机器上的个人设置,还是提交到仓库中的共享设置。
本指南涵盖的内容
下面的表格列出了每项设置及其作用。表格之后的文件树是本页每个代码示例都会引用的示例单体仓库。
本页涉及的设置
以下每项设置都是独立的。它们相互叠加,而不是互相替代,因此可以应用任何适合你仓库的设置。选择从哪里启动 Claude决定了你的设置文件存放在哪里,因此请先阅读它。整合起来展示了将它们全部组合在一起的效果。
| 我想要 | 使用 |
|---|---|
| 只加载你所涉及代码的约定,而不是一个覆盖每个子系统的根文件 | 按目录分层的 CLAUDE.md 文件 |
| 排除你从不涉及的软件包的 CLAUDE.md 文件 | claudeMdExcludes |
| 阻止 Claude 打开构建产物、生成的代码和第三方依赖代码 | permissions.deny 中的 Read 拒绝规则 |
| 通过语言服务器查找某个符号的定义或调用者,而不是扫描文件 | 一个代码智能插件 |
| 当 Claude 创建一个 worktree 时,只检出某个任务需要的目录 | worktree.sparsePaths |
| 从同一个会话中读取和编辑一个相邻的软件包或另一个仓库 | --add-dir 或 additionalDirectories |
| 为 Claude 提供特定于某个区域、只在相关时才加载的流程 | 按目录划分的技能 |
| 用一套所有人都安装的约定,取代大量按目录划分的 CLAUDE.md 文件 | 内部市场中的一个插件 |
示例单体仓库
本页的示例引用了一个拥有三个软件包的单体仓库。同样的模式也适用于大型单一代码库:当某个示例使用 packages/api/ 时,把它替换成你自己的子系统目录,例如 src/backend/ 或 lib/core/。
选择从哪里启动 Claude
你启动 claude 的位置,决定了 Claude 无需额外授权就能读取和编辑哪些文件、启动时会把哪些 CLAUDE.md 文件加载进上下文,以及适用哪些项目设置。
| 启动位置 | 文件访问范围 | 启动时加载的 CLAUDE.md | 适用场景 |
|---|---|---|---|
| 仓库根目录 | 每个文件 | 只有根目录的;子目录的文件会在 Claude 读取那里时按需加载 | 任务跨越多个软件包或子系统 |
| 一个子目录 | 只有该子树,除非你授予更多权限 | 该目录以及每个祖先目录的 | 工作限定于一个软件包或子系统 |
.claude/settings.json 中的项目设置只会从你的启动目录加载,不会像 CLAUDE.md 文件那样从父目录继承:仓库根目录下的 .claude/settings.json,只在你从根目录启动时才会生效。
以下每一节都会说明其设置文件应位于仓库根目录还是你启动所在的子目录中,以及它应该被提交还是保留为本地设置。
按目录分层 CLAUDE.md 文件
在一个大型代码库中,仓库根目录下的单一 CLAUDE.md 往往会趋向于两个极端:不断膨胀以覆盖每个子系统的约定,把上下文成本花在与当前任务无关的指令上;或者保持得过于通用而失去实用性。将指令拆分到按目录划分的文件中,意味着 Claude 只会加载仓库范围的规则,加上你正在处理代码所在的那部分约定。
Claude Code 会在启动时加载你工作目录及其每个父目录中的每一个 CLAUDE.md 文件,然后在它读取某个子目录中的文件时按需加载该子目录的文件。一个根文件设定仓库范围的规则,每个子目录添加自己的规则。
一种常见的划分方式是两个层级:
- 根
CLAUDE.md:适用于所有地方的指令,例如编码标准、提交约定和仓库布局 - 按子目录划分的
CLAUDE.md:特定于该区域技术栈的约定。在一个单体仓库中,这是每个软件包一份。在一个大型单一代码库中,这是每个子系统(例如src/db/或src/api/)一份
将这些文件提交到仓库中,让团队成员能继承它们。每个目录的所有者通常会维护自己的文件。
根 CLAUDE.md 让 Claude 了解仓库结构:
每个子目录的 CLAUDE.md(这里是 packages/api/CLAUDE.md)添加特定于该区域技术栈的上下文:
当你从 packages/api/ 启动 Claude 时,它会加载 packages/api/CLAUDE.md 和根 CLAUDE.md 两者。Claude 会看到本地指令与仓库范围的规则并列,上下文中没有来自 packages/web/ 的指令。对于非单体仓库结构中的任何子目录,同样如此。
有几种方式可以让这些文件随代码库和模型的变化保持更新:
- 在 pull request 中审查:把 CLAUDE.md 的编辑当作任何其他文档更改来对待,让约定跟随代码演进
- 在主要模型发布后重新审视:为规避某个较旧模型局限性而设的指令,一旦更新的模型能自行处理该情况,可能就变成了额外负担。例如,一条强制单文件重构的规则,一旦这个局限消失就可以被删除
- 添加一个提出更新建议的 Stop 钩子:一个
Stop钩子 会在 Claude 完成回复时收到会话记录的路径,因此一个脚本可以审查该会话,并在它暴露出的缺口还很新鲜时提出 CLAUDE.md 更新建议
关于 CLAUDE.md 文件如何加载和相互作用的更多内容,请参阅记忆与项目指令。
在按目录划分的 CLAUDE.md 和路径限定规则之间选择
按目录划分的 CLAUDE.md 文件,以及 .claude/rules/ 下的路径限定规则,都能让你把指令定位到代码树的一部分。它们的区别在于文件存放的位置和加载时机。
| 方式 | 文件位置 | 加载时机 | 适用场景 |
|---|---|---|---|
按目录划分的 CLAUDE.md | 该目录内部,与其代码并列 | 从该目录启动时在启动时加载,或 Claude 在那里读取某个文件时按需加载 | 目录所有者维护自己的约定;指令随代码一起进行版本控制 |
.claude/rules/ 中的路径限定规则 | 仓库根目录的中央 .claude/ | 当 Claude 处理匹配该规则 paths: glob 的文件时 | 你想把所有约定都放在一个地方,或同一条规则适用于许多分散的路径 |
关于也涵盖技能的对比,请参阅比较类似特性。
排除不相关的 CLAUDE.md 文件
当你从仓库根目录启动 Claude 时,每个子目录的 CLAUDE.md 会在 Claude 读取该目录中的某个文件时立即加载。claudeMdExcludes 设置可以按路径或 glob 模式跳过特定文件,让它们永不加载。
对于你从不涉及的目录(例如其他团队的软件包、遗留代码,或第三方子树),可以使用这个设置。这份排除列表是静态的,不是按任务切换的开关。要今天专注于一个软件包、明天专注于另一个,请改为从那个软件包的目录启动 Claude,而不是编辑排除列表。
如果你只想为自己排除这些内容,把这个设置放在 .claude/settings.local.json 中。Claude Code 创建这个文件时会将其加入 gitignore;由于你是在这里手动创建它,请把它加入你的 gitignore。模式使用 glob 语法,与绝对文件路径进行匹配,因此以相对风格开头的模式应以 **/ 开头,以匹配代码树中的任意位置。以下示例排除了其他团队所有的软件包:
这会跳过那些软件包下的每一个 CLAUDE.md 和规则文件。根 CLAUDE.md 以及你确实涉及的软件包仍会正常加载。
以下模式覆盖了其他常见情况:
"**/packages/*/CLAUDE.md":排除每个软件包的 CLAUDE.md,同时保留根文件"**/packages/web/**":排除 web 软件包下的一切,包括规则"/home/user/monorepo/legacy/CLAUDE.md":按绝对路径排除一个特定文件
统一管理策略的 CLAUDE.md 文件无法被排除,因此组织范围的指令始终适用。你可以在任何设置范围(用户、项目、本地或统一管理)中设置 claudeMdExcludes。数组会跨范围合并,因此团队可以设置项目级的默认值,个人再添加本地覆盖。
关于完整的排除文档,请参阅排除特定 CLAUDE.md 文件。
减少 Claude 读取的内容
指令只是最终进入 Claude 上下文的一部分。文件读取是另一项随代码库增长而增加的成本。以下设置会阻止读取不相关的路径,并用语言服务器查找取代穷举式的文件扫描。
阻止读取生成的和第三方依赖代码
Claude 的内容搜索默认遵循 .gitignore,因此已经列在其中的路径(例如 node_modules/、dist/ 和 build/)无需额外配置就不会出现在搜索结果中。
对于那些已被纳入版本控制的路径(例如一个第三方 SDK 或已提交的生成代码),在 permissions.deny 中添加 Read 拒绝规则,即使搜索列出了它们,也能阻止 Claude 打开这些文件。
要为在该仓库中工作的所有人应用这些排除规则,将它们提交到 .claude/settings.json。要让它们保持个人化,改用 .claude/settings.local.json。与本页其他项目设置一样,这些文件只会从你的启动目录加载。如果你从仓库根目录启动 Claude,请把它们放在仓库根目录;如果你从子目录启动,请放在每个软件包的 .claude/ 中。要在每个会话中强制执行相同的拒绝规则,无论启动目录如何,请在统一管理设置中设置它们,用户和项目设置无法覆盖它。
以下示例阻止了构建产物和一个第三方 SDK:
拒绝规则覆盖 Claude 的内置文件工具,以及在被拒绝的路径作为参数传入时能识别的 Bash 文件命令,包括 cat、head、grep 和 find。它们不会把被拒绝的路径从递归搜索的输出中过滤掉,也不会覆盖那些自行打开文件的任意子进程。完整的模式语法请参阅Read 和 Edit 权限规则。
用代码智能减少文件读取
在一个大型代码库中,查找某个符号在哪里定义或被使用,可能要花费大量文件读取和 grep 调用。代码智能插件将 Claude 连接到一个语言服务器,让它能直接跳转到定义、查找引用并呈现类型错误,而不必扫描整个代码树。
官方市场提供了 TypeScript、Python、Go、Rust 和其他常见语言的插件。以下示例安装 TypeScript 插件:
要为仓库中的所有人启用某个插件,而不是自己单独安装,将它添加到enabledPlugins 项目设置中。
代码智能插件需要每位开发者的机器上都安装该语言的语言服务器二进制文件。关于每种语言需要哪个二进制文件,请参阅对照表。从官方市场安装需要能访问 GitHub(该市场托管在那里)的网络。在受限网络中,请改为从内部 Git 主机或本地路径添加市场。
这与上面的 claudeMdExcludes 和 Read 拒绝规则配合得很好。那些设置把不相关的内容排除在上下文之外,而代码智能则阻止 Claude 为了定位某个定义而读遍剩下的内容。
限定 worktree 和文件访问范围
以下设置控制 worktree 中磁盘上有哪些内容,以及 Claude 在你的启动点之外还能读写哪些目录。
只检出你需要的目录
--worktree 标志会在一个新的 git worktree 中启动一个会话,让更改与你的主检出保持隔离。默认情况下它会检出整个仓库。在一个大型仓库中,worktree.sparsePaths 设置会使用 git 稀疏检出,只把列出的目录以及根级文件写入磁盘,因此 worktree 启动更快,占用空间更小。
如果在这个目录中工作的所有人都需要相同的路径,把这个设置提交到 .claude/settings.json。要为你自己添加路径,使用 .claude/settings.local.json:这些列表会跨范围合并,因此一个本地文件可以为已提交的列表添加路径,但不能移除它们。以下示例展示了已提交的文件:
当 Claude 创建一个 worktree 时,它只会检出 .claude/、packages/api/ 和 packages/shared/,而不是整个代码树。sparsePaths 中的路径相对于仓库根目录解析,无论你从哪个子目录启动 Claude。这里可以使用任何目录路径,不仅限于软件包根目录。
这对子智能体 worktree 隔离特别有用。子智能体是为子任务生成的并行 Claude 实例,每个在 worktree 中运行的子智能体都会得到一份轻量级的检出,而不是完整的代码树。一个会话中的所有 worktree 共享相同的 sparsePaths,因此如果一个子智能体需要 packages/api/、另一个需要 packages/web/,请把两者都列出来。
在 sparsePaths 中列出目录,而不是单个文件。像 package.json、tsconfig.base.json 和锁文件这样的根级文件,总是会随你列出的目录一起被检出。根级目录则不会,因此如果你想让仓库根目录的 .claude/settings.json、.claude/rules/ 或 .claude/skills/ 在 worktree 中可用,请把 .claude 加入列表。
要避免像 node_modules 这样的大型目录在各个 worktree 之间重复,可以在同一个 .claude/settings.json 中将 sparsePaths 与 symlinkDirectories 配合使用:
这会创建一个从每个 worktree 的 node_modules/ 指回主仓库副本的符号链接,而不是在磁盘上重复它。
sparsePaths 和 symlinkDirectories 设置会在 worktree 创建之前从你的启动目录读取。创建之后,该会话的工作目录是 worktree 根目录,而不是你启动所在的子目录。因此 worktree 内部的项目设置会从 worktree 根目录的 .claude/settings.json(即仓库根目录文件的检出副本)加载。请把你在 worktree 中需要的其他设置(例如权限规则或钩子)放在仓库根目录的 .claude/settings.json 中。
关于完整的 worktree 设置参考,请参阅Worktree 设置。
授予跨软件包或跨仓库的访问权限
本节适用于你从一个子目录启动 Claude 的情况,或某项任务跨越多个检出的情况。如果你在一个大型单一代码树中从仓库根目录启动,Claude 已经能访问每个文件,可以跳过本节。
当你从 packages/api/ 启动 Claude 时,它可以在该目录内读写文件。如果某项任务需要跨软件包的更改,例如更新一个 api 和 web 都会导入的共享类型,你就需要授予对相邻目录的访问权限。同样的机制也能授予对一个单独检出仓库的访问权限。
.claude/settings.json 中的 additionalDirectories 设置,让 Claude 能访问工作目录之外的目录。以下示例授予了对两个相邻软件包的访问权限:
相对路径相对于你启动 Claude 所在的目录解析。有了这个配置,Claude 就可以在从 packages/api/ 工作的同时,读取和编辑 packages/shared/ 和 packages/web/ 中的文件。
你也可以在启动 Claude 时传入 --add-dir,在不编辑设置的情况下于运行时授予访问权限:
无论你用哪种方式添加一个目录,Claude 都能读取和编辑其中的文件。该目录的 CLAUDE.md、.claude/rules/ 文件和技能是否也会加载,则取决于你添加它的方式:
| 添加方式 | 是否加载 CLAUDE.md 和规则 | 是否加载技能 |
|---|---|---|
additionalDirectories 设置 | 从不 | 从不 |
--add-dir 标志或 /add-dir 命令 | 只在设置了下面的环境变量时 | 是 |
要从一个用 --add-dir 或 /add-dir 添加的目录加载 CLAUDE.md 和规则文件,请设置 CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD 环境变量:
这个环境变量对 additionalDirectories 设置中列出的目录没有影响。详情请参阅从额外目录加载。
对于这个区域中所有人都需要的相邻目录,将 additionalDirectories 提交到 .claude/settings.json。对于个人选择或一次性访问,使用 .claude/settings.local.json,或在启动时传入 --add-dir。
添加按目录划分的技能
任何子目录都可以定义限定于自己技术栈的技能。一个技能会在 Claude 判断它相关时按需加载,因此特定于 API 的工具不会在前端工作期间消耗上下文。
技能存放在该目录内部的 .claude/skills/ 下。将它们与该区域的代码一起提交,让克隆该仓库的任何人都能获得它们。在一个单体仓库中,这可以是每个软件包一套技能。在一个大型单一代码库中,这是每个子系统(例如 src/db/.claude/skills/)一套。
在该子目录内创建一个技能目录:
然后在该目录内编写 SKILL.md,这里是 packages/api/.claude/skills/api-testing/SKILL.md。这个示例教会 Claude 该 API 软件包的测试模式:
不同的子目录以同样的方式存放不同的技能:packages/web/.claude/skills/component-patterns/ 描述的是前端的组件约定,而不是测试。当 Claude 在 packages/api/ 中的某个文件上工作时,它会加载 api-testing 技能。当它在 packages/web/ 中工作时,会改为加载 component-patterns。这两个目录的技能都不会在对方的任务中加载。
你也可以按文件模式(而不是按位置)限定一个技能的范围。paths frontmatter 字段接受 glob 模式,Claude 只会在处理匹配的文件时自动加载该技能。可以把它用于一个存放在仓库根目录 .claude/skills/ 中、但只适用于特定文件(无论它们出现在哪里)的技能,例如一个限定于 **/migrations/** 的数据库迁移技能。
关于创建和组织技能的更多内容,请参阅技能。
保持技能的可发现性
随着技能分散在众多目录中,Claude 可供选择的列表可能会变得很长。Claude 通过阅读每个被发现技能的名称和描述来选择一个技能,只有被选中的技能的完整内容才会加载进上下文。本节介绍如何保持这份列表精简,以及如何撰写能在被缩短后依然有效的描述。
哪些技能处于范围内,取决于你从哪里启动 Claude:
- 从一个子目录启动,例如
packages/api/:来自该目录、直到仓库根目录的每个父目录、以及用户和企业级别的技能 - 从仓库根目录启动:来自该会话期间 Claude 涉及的每个子目录的技能,这个数量可能累积到数百个
- 用
--add-dir添加一个相邻目录之后:那个相邻目录的技能也会加载。additionalDirectories设置只授予文件访问权限,不会加载技能
名称总会加载,但当数量较多时描述会被缩短,这可能会去掉 Claude 用来判断某个技能是否适用的关键词。请保持描述简短,并以某个请求中会包含的词语开头,例如“writing or modifying tests in packages/api/”。
对于许多目录共享的技能(例如 PR 约定或部署清单),把它们放在仓库根目录的 .claude/skills/ 中,这样它们就能从任何启动目录加载。当共享的技能需要自己的版本历史、或必须跨仓库工作时,请改为将它们打包成一个插件。插件技能使用 plugin-name:skill-name 命名空间,因此永远不会与按目录划分的技能冲突。一个平台团队可以在一个地方对它们进行版本管理和更新。
要找出哪些技能未被使用,启用 OpenTelemetry 的日志导出器,并设置 OTEL_LOG_TOOL_DETAILS=1,让技能名称按原样记录,而不是被遮蔽。skill_activated 事件会在其 skill.name 属性中记录每一次调用,invocation_trigger 会记录是命令、Claude,还是某个嵌套技能调用了它,这能告诉你该整合或淘汰什么。
当分层策略不再能扩展时集中管理约定
随着代码库增长,按目录划分的 CLAUDE.md 文件可能变得难以治理。约定会逐渐偏离,文件会过时,也没有人拥有根文件的所有权。解决这个问题通常落在维护该仓库 Claude Code 搭建的团队身上,而不是每个在自己领域工作的开发者。
把约定和参考内容从始终加载的 CLAUDE.md 中移出,转移到按需加载的机制中:
- 技能:Claude 只在与任务相关时才加载的参考资料
- 插件:由一个平台团队集中拥有的、带版本控制的技能、钩子和命令捆包
- MCP 服务器:如果你的组织已经在该仓库上运行了代码搜索或 RAG 索引,把它作为一个 MCP 工具暴露出来,让 Claude 查询它,而不是直接读取文件
关于平台团队如何集中强制执行这些内容,请参阅服务端管理设置还是端点管理设置。
在会话启动时推荐合适的插件
一旦约定存放在插件中,一个团队成员在代码树中不熟悉的部分启动 Claude 时,就没有任何信号能告诉他们该区域的所有者维护的是哪个插件。一个 SessionStart 钩子可以填补这个空缺,因为该钩子打印到 stdout 的任何内容,都会在第一条提示词之前被加入 Claude 的上下文。
例如,你可以编写一个脚本,从钩子输入中读取启动目录,在一份提交到仓库的路径到插件映射表中查找它,并打印出建议,供 Claude 在其第一条回复中转达。请参阅用钩子自动化操作来编写和注册该钩子。
整合起来
以下这份组合配置使用了单体仓库布局。同样的文件也适用于大型单一代码树中的任何子目录。项目设置只会从你启动 Claude 所在的目录加载,因此每个子目录的 .claude/settings.json 必须是自包含的,而不是叠加在一个根文件上。
以下示例将 worktree、additionalDirectories 和 Read 拒绝规则提交到 .claude/settings.json 中,让 packages/api/ 中的每位开发者都获得相同的相邻访问权限、稀疏路径和排除规则。以下文件是 packages/api/ 已提交的分区设置:
由于该会话是从 packages/api/ 启动的,相邻软件包的 CLAUDE.md 文件本来就已经不在范围内,因此这里不需要 claudeMdExcludes。如果你也会从根目录启动会话,请改为把它添加到仓库根目录的 .claude/settings.local.json 中。
additionalDirectories 条目适用于你直接从 packages/api/ 启动 Claude 的情况。在从这个会话创建的 worktree 内部,工作目录是 worktree 根目录,因此这份设置文件不会加载。相邻的软件包在 worktree 内部已经可以访问,无需它,但拒绝规则需要在仓库根目录的 .claude/settings.json 中再放一份副本,才能让 worktree 会话获取到它们,正如worktree 设置说明中所描述的:
搭建完成后,该仓库具有以下布局:
有了这套搭建,从 packages/api/ 启动 Claude 会:
- 加载根 CLAUDE.md 和
packages/api/CLAUDE.md,跳过packages/web/CLAUDE.md - 能在
packages/api/和packages/shared/中读取和编辑文件 - 跳过对
packages/api/中dist/和build/下构建产物的读取 - 让 api-testing 技能按需可用
- 创建包含
.claude/、packages/api/、packages/shared/和根级文件的 worktree,并从根设置文件在整个 worktree 中应用拒绝规则
限定并规划跨软件包的更改
以上配置控制着 Claude 能看到什么。当单次更改涉及多个软件包时(例如更新一个共享类型以及使用它的每个调用点),你如何限定范围和排序这项任务,同样会影响结果。
两种技巧有助于保持跨软件包更改的一致性:
- 在一个会话中把整个更改都交给 Claude:把共享的编辑和它的调用点一起交出去,能让每次编辑背后的决策保持一致,而不是按软件包重新推导
- 在编辑之前把计划保存到一个文件中:先规划,让 Claude 把该计划写入仓库中的一个 markdown 文件。一个长的跨软件包会话会在过程中压缩其上下文,而保存下来的计划能在对话历史可能无法保留的情况下留存下来
后续步骤
搭建好这套配置之后,你可以进一步打磨它:
- 用钩子,在 Claude 编辑文件后运行按目录划分的 linter 或类型检查器
- 阅读有效管理成本,了解代码库规模如何影响 Token 用量,以及如何在更广泛推行之前设置花费限额
- 在 Claude 博客上阅读Claude Code 如何在大型代码库中工作,了解本页所述的单一仓库配置之上的组织级推行模式和所有权模型