Beads 仓库 Required Check Topology 实战:用聚合 CI Gate 解决 GitHub 分支保护与按风险跳过 CI 的矛盾
【免费下载链接】beadsBeads - A memory upgrade for your coding agent项目地址: https://gitcode.com/GitHub_Trending/beads1/beads
导读
本指南以 Beads 仓库的工程文档 engdocs/CI_REQUIRED_CHECK_TOPOLOGY.md 为骨架,系统讲解一套可落地的 GitHub Actions 分支保护拓扑:在"低风险 PR 跳过昂贵检查"的同时,仍然让分支保护只依赖一个稳定、永不被路径过滤或条件触发跳过的聚合检查(aggregate gate)。读完本文,你将掌握 Beads 仓库PR / CI Gate / Required与PR Risk / CI Gate / Required两个聚合门的完整设计、ci-gate.sh评估器的判定规则、Embedded/Server Dolt 矩阵的按层跳过策略,以及 merge queue(merge_group)下必须满足的检查报告约束。
问题:分支保护需要"稳定单门",但 CI 想按风险跳过
GitHub 分支保护(branch protection)通常要求一个或多个 Required Status Check 通过后才能合并。Beads 仓库的诉求是:只要求一个稳定的 PR 门,同时允许 CI 对低风险 PR 跳过昂贵的风险检查(例如只改文档的 PR 不必跑完整 Embedded Dolt 矩阵)。
关键约束在于:被设置为 required 的检查不能来自"带路径过滤(path-filtered)"或"条件触发"的工作流,因为 GitHub 会在整个工作流被路径过滤、分支过滤或 commit-message 跳过指令(如[skip ci])跳过时,把该 required 检查永远留在 pending 状态,PR 因此无法合并。
GitHub 官方语义上的安全区分(Skipping workflow runs、Troubleshooting required status checks)如下:
- 被跳过的整个工作流可能让 required 检查停留在 pending;
- 工作流内部被跳过的单个 job反而会报告
success; - 作为聚合门(aggregate)的 job,如果依赖其他 job,必须使用总是运行的
if: ${{ always() }}条件,否则上游失败会导致聚合 job 本身被跳过; - 任何用于 merge queue 的 required Actions 检查,必须在
merge_group事件上运行。
这是整套拓扑设计的出发点:把"昂贵且按需执行的检查"下沉为工作流内的 job(可跳过、跳过即success),把"必须稳定的门"上升为独立的聚合 job(永远运行、聚合所有叶子 job 的结果)。
当前状态:Beads 仓库的 PR 相关工作流全景
截至文档记录(2026-05-26),仓库中 PR 相关工作流及其触发方式如下(作者以仓库中 .github/workflows 实际文件核对):
| 工作流文件 | 显示名称 | 触发事件 | 角色 |
|---|---|---|---|
| .github/workflows/pr.yml | PR | pull_request(仅main)+merge_group | 基线 PR job、Linux 构建产物、policy/lint 兼容 job、消费 Linux 产物的包门、storage domain/uow 聚焦覆盖,以及基线聚合门PR / CI Gate / Required |
| .github/workflows/pr-risk.yml | PR Risk | pull_request(仅main)+merge_group | Embedded Dolt 风险检测、embedded 构建/测试分片、Nix flake smoke,以及风险聚合门PR Risk / CI Gate / Required |
| .github/workflows/main.yml | Main | push到main | main 分支健康检查、包门、平台 smoke/short 覆盖、embedded Dolt 覆盖、提升后的 Linux no-short 集成分片 |
| .github/workflows/regression.yml | Regression Tests | pull_request、push到main、手动 dispatch;当前不跑merge_group | 使用 job 级条件回归执行 |
| .github/workflows/cross-version-smoke.yml | Cross-Version Smoke Tests | 每个面向main的 PR、tag push、手动 dispatch;当前不跑merge_group | 跨版本升级冒烟 |
| .github/workflows/nix-build.yml | nix build | pull_request/push上使用工作流级paths过滤 | 不得被直接设为 required |
| .github/workflows/update-vendor-hash.yml | Update vendorHash for dependabot Go bumps | pull_request_target(Dependabot Go 升级) | 会改动 Dependabot 分支,不得作为 required PR 检查 |
另外,live 的默认分支 ruleset(gastownhall/beads仓库的Protect main - light (beads and gastown))目前只强制删除保护与非快进保护,尚未要求任何状态检查——这正是本文所述聚合门后续要接管的缺口。
Required Check 契约:只要求聚合门,不直接要求叶子 job
分支保护应指向"来自未过滤工作流的稳定聚合 Actions 检查"。最初的单检查方案假设所有 PR job 都生活在一个工作流里;工作流拆分后,工作流内部的聚合门只能覆盖同一工作流内的 job,因此第一次上线采用"每个 required 工作流一个聚合门":
- 基线聚合候选:
PR / CI Gate / Required - 风险聚合候选:
PR Risk / CI Gate / Required - 来源:GitHub Actions
- 应用于:面向
main的 pull request 与 merge queue 组
而以下这些现有检查名不应被直接设为 required,它们应保持可见以便诊断,分支保护只指向聚合门:
Detect CI tierCheck build-tag policyCheck pure-Go and js/wasm boundaries (CGO_ENABLED=0)Check version consistencyCheck doc flags freshnessCheck for .beads changesTest (ubuntu-latest)、Test (macos-latest)、Test (storage domain + uow)Build (Embedded Dolt)Test (Embedded Dolt Storage 1/5)至Test (Embedded Dolt Storage 5/5)Test (Embedded Dolt Cmd 1/20)至Test (Embedded Dolt Cmd 20/20)Test (Windows - smoke)Check formatting、LintTest Nix FlakeDifferential Regression (v0.49.6 baseline)Upgrade smoke (<version> -> candidate)Resolve versions to testnix build .#default
从仓库当前源码看,.github/workflows/pr.yml 中的ci-gatejob 的needs已实际聚合了 22 个叶子 job(含build-artifacts、check-build-tags、check-cmd-bd-puregeo-tests、test-windows-liveness、worktree-remove-windows、check-version-consistency、check-migration-hygiene、check-doc-flags、check-doc-freshness-platforms、pr-preflight-platforms、check-no-beads-changes、detect-package-gates、package-mcp、package-npm、pr-policy-wrapper、pr-core-wrapper、pr-lint-wrapper、test-domain-uow、contract-corpus、fmt-check、lint、windows-make-shell),而complexity-report、build-examples、test-macos、test-windows-dbproxy-server等建议性(advisory)job 刻意不进聚合门——这印证了文档中"推广一个新 job 进 required 集合是维护者决策"的原则。
工作流拓扑三原则
1. Required 工作流保持无条件触发
.github/workflows/pr.yml 是 required 基线工作流的拥有者,其 PR 与 merge queue 触发器必须保持无过滤:
on: pull_request: branches: [ main ] merge_group:不要给pr.yml或pr-risk.yml添加paths、paths-ignore或更窄的分支过滤。路径与风险决策应交给 detector job 和 job 级if条件完成,而不是工作流级过滤。
2. 添加聚合 Gate job
pr.yml中基线聚合门的历史初始快照如下(注意:这是历史快照,不再与后续新增/重命名的叶子 job 同步;当前接线以 .github/workflows/pr.yml 及其结构性测试为准):
ci-gate: name: CI Gate / Required runs-on: ubuntu-latest needs: - build-artifacts - check-build-tags - check-cmd-bd-puregeo-tests - check-version-consistency - check-no-duplicate-migrations - check-doc-flags - check-no-beads-changes - detect-package-gates - package-mcp - package-npm - package-website - pr-policy-wrapper - pr-core-wrapper - pr-lint-wrapper - test-domain-uow - fmt-check - lint if: ${{ always() }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Evaluate CI gate env: CI_GATE_NAME: PR baseline gate CI_GATE_REQUIRED: >- BUILD_ARTIFACTS CHECK_BUILD_TAGS CHECK_CMD_BD_PUREGEO_TESTS CHECK_VERSION_CONSISTENCY CHECK_NO_DUPLICATE_MIGRATIONS CHECK_DOC_FLAGS CHECK_NO_BEADS_CHANGES DETECT_PACKAGE_GATES PACKAGE_MCP PACKAGE_NPM PACKAGE_WEBSITE PR_POLICY_WRAPPER PR_CORE_WRAPPER PR_LINT_WRAPPER TEST_DOMAIN_UOW FMT_CHECK LINT BUILD_ARTIFACTS: ${{ needs.build-artifacts.result }} CHECK_BUILD_TAGS: ${{ needs.check-build-tags.result }} CHECK_CMD_BD_PUREGEO_TESTS: ${{ needs.check-cmd-bd-puregeo-tests.result }} CHECK_VERSION_CONSISTENCY: ${{ needs.check-version-consistency.result }} CHECK_NO_DUPLICATE_MIGRATIONS: ${{ needs.check-no-duplicate-migrations.result }} CHECK_DOC_FLAGS: ${{ needs.check-doc-flags.result }} CHECK_NO_BEADS_CHANGES: ${{ needs.check-no-beads-changes.result }} DETECT_PACKAGE_GATES: ${{ needs.detect-package-gates.result }} PACKAGE_MCP: ${{ needs.package-mcp.result }} PACKAGE_NPM: ${{ needs.package-npm.result }} PACKAGE_WEBSITE: ${{ needs.package-website.result }} PR_POLICY_WRAPPER: ${{ needs.pr-policy-wrapper.result }} PR_CORE_WRAPPER: ${{ needs.pr-core-wrapper.result }} PR_LINT_WRAPPER: ${{ needs.pr-lint-wrapper.result }} TEST_DOMAIN_UOW: ${{ needs.test-domain-uow.result }} FMT_CHECK: ${{ needs.fmt-check.result }} LINT: ${{ needs.lint.result }} run: | skipped_ok="" if [[ "$GITHUB_EVENT_NAME" == "merge_group" ]]; then skipped_ok="CHECK_NO_BEADS_CHANGES" fi export CI_GATE_SKIPPED_OK="$skipped_ok" bash .github/scripts/ci-gate.shpr-risk.yml则有一个针对detect-ci-tier、build-embedded、test-embedded-storage、test-embedded-cmd、test-nix的配套聚合门。
ci-gate.sh评估器:判定规则
.github/scripts/ci-gate.sh 是一个很小的 shell 求值器(set -euo pipefail,从needs.*.result聚合)。它对每个必需变量按result值分类判定:
success→ 通过;skipped→ 仅当该变量在CI_GATE_SKIPPED_OK白名单中才通过,否则失败;failure/cancelled→ 失败;- 未设置(
"")→ 失败("unset"); - 其他意外值 → 失败("unexpected result")。
核心的is_skipped_ok()函数用[[ "$skipped_ok_vars" == *" $var "* ]]做精确的空格分隔匹配,避免BUILD_EMBEDDED与BUILD_EMBEDDED_X之类的误判。任何失败都会通过::error::注解输出到日志,最终exit 1,聚合门变红。
允许skipped的合法场景包括:
CHECK_NO_BEADS_CHANGES=skipped在merge_group上可接受(该 job 是 PR 专属,见 pr.yml 中check-no-beads-changes的if: github.event_name == 'pull_request');- 在风险聚合中,
BUILD_EMBEDDED、TEST_EMBEDDED_STORAGE、TEST_EMBEDDED_CMD仅在FULL_EMBEDDED != true时可skipped(见 pr-risk.yml 中 ci-gate 对CI_GATE_SKIPPED_OK的动态赋值); - 其余基线 job 必须全部
success。
这样分支保护指向稳定聚合 job,同时保留底层 job 名称与日志便于诊断。
3. 风险决策保持在 job 级
条件风险检查应使用如下模式(detector 输出 → job 级if→ 聚合门always()):
detect-risk: name: Detect risk outputs: run_risk: ${{ steps.detect.outputs.run_risk }} risk-check: name: Risk check needs: detect-risk if: needs.detect-risk.outputs.run_risk == 'true' ci-gate: name: CI Gate / Required needs: [detect-risk, risk-check] if: ${{ always() }}required 聚合门应仅当detect-risk.outputs.run_risk != true时把risk-check=skipped视为成功;若 detector 想要跑风险检查而它被跳过、失败或被取消,聚合门必须失败。
禁止在 required 检查上使用这种工作流级路径过滤:
on: pull_request: paths: - 'go.mod' - 'go.sum'若该工作流或其某个 job 被设为 required,不触碰这些路径的 PR 会一直等待 GitHub 永远不会创建的检查,永久阻塞。
条件检查的落点:各矩阵的具体处理
Embedded Dolt 矩阵
当前 embedded Dolt 拓扑已契合 required-check 模型:
detect-ci-tier总是运行;build-embedded、test-embedded-storage、test-embedded-cmd使用 job 级if;- .github/scripts/ci-embedded-tier.sh 对
push、merge_group、PR diff 边界不可用、以及命中风险路径时运行完整 embedded 覆盖。它通过git diff --name-only base head检查变更路径,命中cmd/*、internal/*、tests/*、scripts/*、.github/scripts/*、.github/workflows/*、*.go、go.mod/go.sum、Makefile、default.nix、flake.nix、flake.lock、packages.nix及核心文档(AGENTS.md等)即判定为风险路径; - 仅文档 PR 可以跳过 embedded 矩阵,不会让 required 门悬置,因为聚合 job 仍然运行。
仓库结构性测试 scripts/ci_workflow_test.go 正是为这套契约兜底:它解析pr.yml/pr-risk.yml/main.yml,断言ci-gate的if为${{ always() }}、needs与CI_GATE_REQUIRED、env result 映射三者一致,并断言各矩阵 job 的strategy.fail-fast必须为false(防止单个慢/抖动分片取消兄弟分片)。
Server Dolt Storage 矩阵
test-server-storage-full镜像test-embedded-storage的分片方式,在同一工作流中降一档:
- job 级
if复用与 embedded 矩阵相同的detect-ci-tier门; - .github/scripts/server-storage-test-shard.sh 从
internal/storage/dolt/*_test.go发现顶层Test*函数(排除TestConformance,它由独立的test-server-storagejob 用-test.run '^TestConformance$'子测试路径分片),通过提交在仓库中的 .github/scripts/server-storage-test-shards.txt 清单分配已知重型测试,其余按hash(name) % total散列分配——与embedded-storage-test-shard.sh相同的"清单 + 散列回退"机制; - 16 个分片(vs embedded 的 5 个):server 模式是真实的 socket 往返,针对容器化 Dolt server 每次测试执行
CREATE/DROP DATABASE,且该包顶层测试数是 embedded 套件的 3.5 倍(1126 vs 324)。此前未分片的单 job(Go 15m 超时、timeout-minutes: 20)从未跑完——在 256/1126 个测试处因超时死亡,零失败纯属时间不足。其中TestCloudAuthCLIRouting一个测试就要约 9.5 分钟,被清单单独钉在 shard 1,避免拖累其他分片; fail-fast: false,与该工作流中所有矩阵 job 一致。
回归测试(Regression)
Regression Tests可以保持为非 required工作流。若回归要影响合入门,不要直接 requiredDifferential Regression (v0.49.6 baseline),改用以下窄方案之一:
- 把回归 detector 与回归 job 移入 required PR 拓扑,接入对应聚合门,并补充默认运行回归的
merge_group行为; - 保持
regression.yml独立,移除工作流级 skip 过滤,添加merge_group,追加一个最终Regression Gate / Informational聚合门,并保持非 required(除非有意扩展分支保护)。
首选拓扑只把聚合门设为 required。
Nix Build
.github/workflows/nix-build.yml 当前使用工作流级paths过滤(pull_request与push均有)。保持nix build .#default非 required。
若完整 Nix 构建必须影响可合并性,应把它移入未过滤的 required PR 工作流,置于 detector 与 job 级if之后,并教会聚合门何时接受"被跳过的 Nix build"。不要直接 required 路径过滤的nix build工作流或nix build .#defaultjob。
跨版本冒烟(Cross-Version Smoke)
Cross-Version Smoke Tests对普通 PR 应保持非 required,除非维护者明确选择在聚合门中支付该成本。若变为 required,需添加merge_group并置于 required 拓扑内的 detector + 聚合之后。不要直接 required 矩阵展开的Upgrade smoke (<version> -> candidate)job。
Merge Queue 行为:merge_group是不可省略的一环
Required 聚合检查必须为merge_group报告。否则 GitHub 可以先把 PR 入队,然后因 required 检查从未对合成 merge group 提交报告而无法合并。
对merge_group的策略:
PR与PR Risk必须包含merge_group触发器;detect-ci-tier应把merge_group视为完整 embedded 覆盖(ci-embedded-tier.sh 的case分支已实现:merge_group → full_embedded=true);- 任何加入 required 拓扑的风险 detector 都应默认在
merge_group上运行——因为 merge group 提交可能把各自安全的 PR 组合成有风险的集成状态; - 聚合门应像处理 PR 结果一样处理 merge group 结果,唯一例外是 PR 专属的卫生检查(如
Check for .beads changes)可以按设计跳过(pr.yml的 ci-gate 在GITHUB_EVENT_NAME == "merge_group"时把CHECK_NO_BEADS_CHANGES加入CI_GATE_SKIPPED_OK)。
首次上线清单与回滚步骤
首次上线快照(历史决策上下文)
以下清单记录了最初的部署计划,作为决策背景保留,不是当前部署流程:工作流接线已实现,第 7、8 步的分支保护/ruleset 策略变更仍是维护者的待决事项。
- 向 required PR 工作流添加 .github/scripts/ci-gate.sh 与聚合门 job(最初在分支
ci/bd-am3.1-wrapper-commands上开发); - 开一个 PR,验证新聚合检查名在 GitHub Actions 中精确出现;
- 验证仅文档 PR(embedded job 被跳过)时聚合门成功;
- 验证风险 PR 或手动测试分支(embedded job 运行且通过)时聚合门成功;
- 验证一个刻意失败的底层 job 会使对应聚合门失败;
- 验证 merge queue 运行在 merge group 上报告聚合门;
- 更新默认分支 ruleset 或分支保护,只要求来自 GitHub Actions 的聚合门;
- 移除对单个 CI、回归、Nix、跨版本 job 名的直接 required。
回滚步骤
- 从默认分支 ruleset 或分支保护中移除聚合门检查;
- 恢复此前存在的 required 检查列表(若有);
- 还原添加聚合门 job 与评估器的工作流提交;
- 确认新的 PR 不再等待聚合门。
如果回滚是因为聚合逻辑有误,优先先放宽分支保护移除聚合要求——这能在不隐藏用于诊断的失败工作流日志的前提下解阻塞合并。
Commit-Message 跳过指令与 fail-closed 保障
上述拓扑解决了路径过滤与分支过滤导致的 pending required 检查。但当 HEAD 提交信息包含[skip ci]等跳过指令时,GitHub 仍可跳过push与pull_request工作流。若维护者需要"提交信息跳过也必须 fail closed(而不是 pending)"的硬保证,required 检查必须由一个不受这些指令跳过的可信小报告器发出,例如:一个pull_request_target工作流——不 checkout、不运行 PR 代码,在检查不受信的pull_request工作流结果后,于 PR head SHA 上创建名为CI Gate / Required的 check run。
该报告器刻意不在第一次窄上线范围内。在此之前,不要对面向main的 PR 使用 commit-message 跳过指令。
小结:一份可直接复用的聚合门设计蓝图
Beads 仓库的 required-check 拓扑把"分支保护只认一个稳定门"与"CI 按风险省钱"统一起来:required 工作流永远无过滤触发,风险决策全部下沉到 detector + job 级if,ci-gate.sh作为小型判定器把needs.*.result收敛为单一结论,always()保证聚合 job 即使上游全跳也会运行并给出明确 verdict。配合 scripts/ci_workflow_test.go 这类结构性测试,工作流拓扑本身也被纳入了 CI 的可验证范围。如果你的仓库同样面临"路径过滤工作流导致 required 检查悬置"的经典困境,这套"每 required 工作流一个聚合门 + skipped 白名单 + merge_group 全量覆盖"的方案可以直接照搬落地。
【免费下载链接】beadsBeads - A memory upgrade for your coding agent项目地址: https://gitcode.com/GitHub_Trending/beads1/beads
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考