12k
All articles

使用 CODEOWNERS 实现代码自动审查

配置 GitHub CODEOWNERS,避免静默失效,并通过正确的路径规则、权限和分支保护强制代码审查。

OpenReplay Team
OpenReplay Team
使用 CODEOWNERS 实现代码自动审查

CODEOWNERS 文件是仓库中的一个纯文本文件,用于将路径模式映射到所有者(GitHub 用户或团队),并在拉取请求涉及匹配路径时自动向这些所有者发起审查请求。它同时承担两项职责:无需手动通知,即可将审查请求路由至正确的负责人,同时也记录了各部分代码的归属关系。然而,CODEOWNERS 的失败是静默的。错误的模式顺序、没有成员的团队,或者没有写入权限的所有者,都不会触发任何错误提示——审查请求会悄然消失,PR 在没有预期审查者介入的情况下直接合并。

本指南将在几分钟内完成基础配置,然后将重点放在常见陷阱上:静默覆盖具体规则的”最后匹配优先”规则,以及让 CODEOWNERS 看似已配置却实际上什么都不做的权限问题、空团队问题和默认分支问题。

核心要点

  • CODEOWNERS 遵循最后匹配优先原则:当多个模式匹配同一文件时,只有最后一条匹配行才会分配所有者——应将通用规则置于顶部,将具体的覆盖规则置于底部。
  • 该文件必须位于 PR 的基础分支上,大小须在 3 MB 以内,路径大小写须正确,且语法须合法;任何无效行都会被静默跳过。
  • 仅凭 CODEOWNERS 无法阻止合并——还必须在规则集或分支保护规则中同时启用”合并前需要拉取请求”和”需要代码所有者审查”两项设置。
  • 没有写入权限的所有者会被静默忽略;团队所有者本身必须可见且具有写入权限,即使其每位成员已单独拥有写入权限也不例外。
  • GitHub CODEOWNERS 支持 ! 取反——类似 !README.md 的模式会被视为无效而拒绝。

CODEOWNERS 文件的作用是什么?

CODEOWNERS 用于指定负责仓库中特定路径的个人或团队。当有人提交的拉取请求修改了匹配路径时,GitHub 会自动向列出的所有者发起审查请求。该文件格式同样适用于 GitHub、GitLab 和 Bitbucket;本文示例以 GitHub 为主。

随着越来越多的 PR 由 AI 智能体提交,在敏感路径(如 auth/**/migrations/、CI 配置)上设置 CODEOWNERS 规则,可以确保在智能体的变更落地之前,仍由人工所有者进行审查。

如何配置和强制执行 CODEOWNERS?

将文件放置于 .github/CODEOWNERS。GitHub 会依次查找 .github/、仓库根目录和 docs/,并使用找到的第一个 CODEOWNERS 文件,因此使用单一规范位置可以避免混淆。每行按 pattern @owner 格式编写一条规则,然后将其提交到默认分支

# .github/CODEOWNERS
# 仓库中所有内容的默认所有者
*                   @my-org/core-team

# 按领域划分前端和后端
/src/frontend/      @my-org/frontend-team
/src/backend/       @my-org/backend-team

# 测试和文档
**/tests/           @my-org/qa-team
*.md                @my-org/docs-team

# 敏感路径指定专属所有者(置于末尾)
/src/auth/          @my-org/security-team

像提交其他文件一样推送它:

git add .github/CODEOWNERS
git commit -m "Add CODEOWNERS"
git push origin main

提交该文件仅会触发审查请求——并不会阻止任何操作。若要真正拦截合并,需同时启用两项设置:合并前需要拉取请求需要代码所有者审查。可在较新的 Rulesets(Settings → Rules → Rulesets)或经典的分支保护规则(Settings → Branches)中进行配置。两种方式均可使用,Rulesets 是更新的配置入口。

值得掌握的 CODEOWNERS 语法模式

其模式语言遵循大多数 gitignore 规则。以下五种模式几乎涵盖所有场景:

*                   @core-team      # 全局默认
/api/               @backend-team   # 指定目录
*.ts                @frontend-team  # 任意深度的指定扩展名文件
**/tests/           @qa-team        # 任意位置的嵌套目录
/security/          @sec-team @compliance-team   # 一行指定两个所有者

*.ts 这样的非锚定扩展名 glob 会匹配仓库中任意位置的该类型文件——其行为与 **/*.ts 相同。列出多个所有者时有一条规则需要注意:所有所有者必须写在同一行。如果分行书写,该模式只会匹配最后提及的所有者。当需要代码所有者审查时,列出的所有者中任意一人批准即可满足要求。

有一个常见误解需要澄清:与 .gitignore 不同,GitHub CODEOWNERS 支持 ! 取反。类似 !README.md 的模式会被视为无效而拒绝——GitHub 官方文档明确指出,! 取反、[ ] 字符范围和 \# 转义均为 gitignore 特性,在此处不适用。若要排除某个路径,可为其分配不同的所有者,或通过规则排列使其不匹配任何规则(在后续更具体的行中将所有者列留空,即可取消该路径的所有权)。

最后匹配优先规则(最常见的错误)

CODEOWNERS 遵循最后匹配优先原则:当多个模式匹配同一文件时,只有最后一条匹配行才会分配所有者。 规则顺序是最常见的失败原因。应将通用规则置于顶部,将具体的覆盖规则置于底部。

以下是错误的顺序——通配符排在最后,静默地覆盖了所有规则:

# 错误 —— * 是最后匹配项,因此 @core-team 也会成为 /src/auth/ 的所有者
/src/auth/          @security-team
*                   @core-team

由于 * 匹配 /src/auth/app.ts,且在文件中出现得更靠后,@security-team 永远不会被请求审查。将顺序调换:

# 正确 —— 通用规则在前,具体覆盖规则在后
*                   @core-team
/src/auth/          @security-team

现在,/src/auth/ 下的变更会请求 @security-team 审查,其他所有变更则回退到 @core-team。每次新增规则时都应检查模式顺序。

CODEOWNERS 静默失效的原因

大多数”已配置但没有任何效果”的问题都源于以下几种情况。CODEOWNERS 从拉取请求的基础分支读取,区分大小写,大小须在 3 MB 以内,且会跳过任何语法无效的行——因此,一个看起来正确的文件仍可能完全不触发任何审查请求。

现象原因解决方案
完全没有请求审查者文件不在 PR 的基础分支将 CODEOWNERS 提交到合并目标分支
某条具体规则从不生效最后匹配优先——后面的模式覆盖了它将通用规则上移,将具体规则下移
某行被忽略,其余正常该行存在语法错误——被静默跳过在 GitHub 上打开文件;“Syntax errors”链接会标记出错误行
所有者已列出但从未被请求所有者缺少写入权限,或用户/团队不存在授予写入权限;验证用户名或团队名
合并被阻止,无人可批准空团队拥有该路径为团队添加至少一名成员
路径不匹配任何规则没有规则覆盖该路径添加规则,或接受任意有写入权限者的批准
草稿 PR 没有触发请求草稿 PR 不会触发代码所有者请求将 PR 标记为”准备好审查”
大文件中的规则被忽略CODEOWNERS 超过 3 MB 未被加载使用通配符合并条目

两个权限细节导致了大多数静默失败。所选的代码所有者必须具有写入权限——缺少写入权限的所有者会被静默忽略。当所有者为团队时,该团队本身必须可见且具有写入权限,即使其每位成员已单独拥有写入权限也不例外。如果指定了不存在或无访问权限的用户或团队,则不会分配任何代码所有者,且 PR 上不会有任何警告提示。GitHub 会标出错误行:在仓库界面中打开 CODEOWNERS 文件即可查看高亮显示的错误,也可通过 REST API 获取这些信息。

超越静态分配:团队自动分配与 Actions

CODEOWNERS 以静态方式将路径映射到所有者。当这种方式无法满足需求时,有两种机制可以对其进行扩展。

内置团队自动分配功能可以避免通知整个团队。在 Organization → Teams → team → Settings → Code review 中,启用自动分配:每当团队被请求审查时,整个团队的请求会被移除,转而分配给其中的部分成员。可选择**轮询(round robin)模式(按最近请求时间轮换)或负载均衡(load balance)**模式(平衡每位成员近期的请求总量)。需注意一个交互行为:当分支保护要求必须有代码所有者审查时,团队请求无法被移除,因此个人请求会在团队请求之外额外出现。

仅在分配逻辑依赖于 diff 内容或标签时才使用 GitHub Actions——这是 CODEOWNERS 无法表达的场景。以下是一个使用 actions/checkout(最新版本 v7.0.0,发布于 2026 年 6 月 18 日)和审查者分配 action 的最简工作流,触发器为普通的 pull_request 事件:

name: Assign Reviewers
on:
  pull_request:
    types: [opened, ready_for_review]
permissions:
  pull-requests: write
jobs:
  assign:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
        with:
          fetch-depth: 0   # 跨分支 git diff 所需
      # ...在此处将变更路径或标签映射到审查者

将以下升级路径作为决策依据,而非可随意选择的菜单:CODEOWNERS 用于静态的路径→所有者规则,团队自动分配用于在团队内部分散负载,Actions 用于基于变更内容或标签的动态逻辑。

从默认分支上的单个 .github/CODEOWNERS 文件开始,按照从通用到具体的顺序排列规则,启用”需要代码所有者审查”,然后提交一个测试 PR,确认预期所有者已被请求——这一步检查可以在静默失败进入生产环境之前将其捕获。

常见问题

CODEOWNERS 与 GitHub 团队代码审查自动分配有什么区别?

CODEOWNERS 是一个静态文件,将路径模式映射到所有者,并在 PR 涉及匹配路径时发起审查请求。团队自动分配是一项组织级设置,一旦某个团队被请求审查,会将整个团队的请求替换为通过轮询或负载均衡方式选出的部分成员请求。两者可以协同工作:CODEOWNERS 决定哪个团队负责某个路径,自动分配则决定该团队中哪些成员实际收到通知。

能否像 gitignore 一样使用取反符号来排除特定文件?

不能。GitHub CODEOWNERS 不支持取反,因此类似 '!README.md' 的模式会被视为无效,该行会被静默跳过。GitHub 文档明确指出,'!' 取反、'[ ]' 字符范围和 '#' 转义均为 gitignore 特性,在此处不适用。若要排除某个路径,可添加一条更靠后、更具体的规则为其分配不同的所有者,或在该具体行的所有者列留空,以取消该路径的所有权。

为什么我的拉取请求没有请求代码所有者审查,即使文件看起来配置正确?

最常见的原因是 CODEOWNERS 从 PR 的基础分支读取,因此仅存在于功能分支上的文件不会生效。其他静默失败的原因包括:所有者缺少写入权限、团队所有者不可见或缺少写入权限、团队为空、PR 处于草稿状态(草稿 PR 不会触发代码所有者请求)、文件超过 3 MB、路径大小写错误,或存在被 GitHub 静默跳过的无效行。

CODEOWNERS 本身能阻止合并吗,还是需要配合分支保护使用?

CODEOWNERS 本身只会发起审查请求,不会阻止合并。若要拦截合并,还必须同时启用两项设置:'合并前需要拉取请求'和'需要代码所有者审查'。可在 Settings → Rules → Rulesets 的规则集中,或在 Settings → Branches 的经典分支保护规则中进行配置。两种方式均可使用,Rulesets 是更新的配置入口。

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — self-hosted, with full data ownership.

Star on GitHub

We use cookies to improve your experience. By using our site, you accept cookies.