工作流能够为合并决定提供证据,但只有 YAML 并不会让这些证据成为强制要求。可靠的构建门禁需要连接正确的事件、实际测试的准确版本、有意义的测试结果,以及要求对应检查通过的仓库规则。审阅时应把这些环节一起核对,不能看到早先某次运行有绿色标志,就认为这次拟合并的内容已经满足当前要求。

区分执行与强制要求

.github/workflows/ 下的文件描述触发器和作业。on 选择事件,分支保护或规则集则另行决定合并之前必须通过哪些检查。应记录稳定的检查名称,并在改名时同时审阅相应规则。不同工作流也应避免产生含糊的同名作业;如果规则指定检查来源,还需确认结果确实由预期的可信应用提供。

还要查看合并请求检查框显示的版本。pull_request 运行通常检查的是合成的合并提交,而不只是贡献者分支末端。合并队列为候选组合生成检查时,需要独立的 merge_group 事件。本地构建成功,或者手动触发过某份工作流,都不能自动替代规则要求的事件与版本;检查对象不一致时,即使输出相同,也不构成同一次验证。

明确依赖结果

没有依赖的作业可以并行运行,单个作业里的步骤通常顺序执行。needs 建立前置关系,条件控制失败或跳过之后哪些工作仍会执行。只有一个必需测试作业的工作流,并不需要人为增加依赖作业,也不必额外添加事件条件。因此,把缺少这些字段一律判成错误的检查器,无法准确判断门禁是否有效。

如果使用汇总门禁,应等待每个必需作业,并显式检查其结果。通过 if: ${{ always() }} 避免前置失败后只把门禁跳过,再要求结果确实为成功。如果某项可选工作允许跳过,应明确编码这个例外,而不是把所有缺失结果都视为通过。必需测试也不能随意开启忽略错误,否则一条普通的成功提示就可能掩盖没有完成的验证。

权限声明约束令牌,不约束所有凭据

permissions 控制的是 GITHUB_TOKEN,不会限制另行传给命令的其他秘密凭据。严格的工作流级声明可以由作业继承,不需要每个作业机械重复同样内容。只有真正需要的地方才增加权限。下面的离线实验没有检出私有源码,也不调用 GitHub 接口,因此不使用令牌;真实项目接入源码时,应重新检查实际最低权限需求。

普通的复刻仓库合并请求通常收到只读令牌,而不会收到仓库秘密,具体仍受仓库设置影响。“秘密只读”不是准确说法,令牌权限与秘密可用性是两个问题。不能为了取得秘密,就改用具有更高权限的 pull_request_target 并执行不可信的请求代码。日志遮盖只是辅助保护,不是打印凭据的许可,也不保证经过转换后的值绝不会泄漏。

坏掉的样子

整个工作流因路径或分支过滤没有运行时,对应必需检查可能一直等待;作业因条件而跳过,却可能在没有执行测试的情况下满足检查规则。这是两种不同结果。修改检查名称时应小心,并在托管服务上验证失败、跳过和取消路径,不能只用一次成功运行证明规则配置正确。检查能否出现、是否真正运行和能否阻止合并,需要分别确认。

缓存需要兼容的键,也需要按依赖声明执行可靠安装。普通合并请求产生的缓存属于该请求的合并引用,仅仅键相同,并不意味着基础分支可以恢复它。不能笼统声称任何复刻请求缓存都能污染主分支;应结合事件、引用范围和信任关系判断风险。另外,测试取消与部署恢复也不同:取消正在运行的作业,不会自动撤销它已经产生的外部变化,重试必须了解实际完成到了哪里。

快速验证

下面实验单独检查一项汇总门禁决定:只有前置作业的结果严格等于 success 才接受。在真实工作流中,这个值应来自 needs.test.result,并通过 needs 声明前置依赖。这里的输入则刻意采用合成状态,是本地判断练习,不是工作流校验器,也不能替代项目真实测试。它不会安装工作流或启动托管运行,只让这一项判定逻辑能够独立复现。

把下面仅用标准库的探针保存为 gate_check.py,在有 Python 3 和 Bash 的环境运行 python3 gate_check.py。它向同一条 Shell 判断传入合成结果,并包括一个故意失败的测试断言。程序不写文件,不访问任何服务,也不使用真实仓库凭据;需要的解释器和 Shell 必须已经可用。

import os
import subprocess
import sys

subprocess.run([sys.executable, "-c", "assert 2 + 2 == 4"], check=True)
negative = subprocess.run([sys.executable, "-c", "assert 2 + 2 == 5"],
                          capture_output=True, text=True)
assert negative.returncode != 0 and "AssertionError" in negative.stderr
print("test-negative-control: rejected")
command = 'test "$TEST_RESULT" = success'
for result in ("success", "failure", "cancelled", "skipped", "", "unexpected"):
    run = subprocess.run(["bash", "--noprofile", "--norc", "-c", command],
                         env={"PATH": os.environ["PATH"], "TEST_RESULT": result},
                         capture_output=True, text=True)
    accepted = run.returncode == 0
    assert accepted == (result == "success"), (result, run)
    print(f"{result or '<missing>'}: {'accept' if accepted else 'reject'}")
print("PASS: local predicate only; GitHub enforcement not exercised")

预期故意失败的测试被拒绝,只有 success 被接受,最后输出范围提醒。缺失、异常、失败、取消及跳过结果都不能被当成真实成功。这证明了本地命令和拒绝分支,但没有解析任意 YAML、求值 GitHub 表达式、调度作业、读取仓库规则,也没有证明线上合并会被阻止。这些仍需针对真实仓库配置进行托管集成检查,不能用离线探针的成功代替。

延伸阅读

来源