fix(ci): 下游 job 改用显式状态函数,filter 失败不再静默跳过七个核心闸门 (#4928) - #5344
Merged
Conversation
`ci.yml` 的 7 个下游 job 原本都是 `if: needs.filter.outputs.<x> == 'true'`。
`if:` 里不出现状态函数时,GitHub 会隐式包一层 `success()`,于是 `filter` 一旦
失败(checkout 抖动、dorny/paths-filter 故障、10 分钟超时),test /
temporal-conformance / dogfood / dogfood-verify / build-core / build-docs /
console-pin 会被一起跳过 —— 而 skipped 在分支保护里算通过。结果是零测试运行、
全绿可合、没有任何红色信号。
七处统一改成「只有过滤器明确说 false 才跳」:
if: ${{ !cancelled() && needs.filter.outputs.<name> != 'false' }}
这与 filter 自身 outputs 上 `|| 'true'` 的取舍完全一致 —— 存疑就全跑。每个
job 保留自己的输出名(core / docs / console)。
聚合闸门补上同一层论证:test-gate 与 dogfood-gate 把 `filter` 纳入 needs,
当某条腿是 skipped 而 `filter` 未成功时判红,不再无条件接受 skipped。这条
分支在新 `if:` 下不可达,留作不变量的常驻断言 —— 它守的失败模式是静默的。
`cancelled` 两侧仍然放行(#3668 的 run-lifecycle 论证)。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FTszibd6C8sUCCZnM4VcrL
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
os-zhuang
marked this pull request as ready for review
August 5, 2026 00:23
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #4928
改了什么
ci.yml的 7 个下游 job 原本都是if: needs.filter.outputs.core == 'true'这个形状。if:里不出现状态函数时,GitHub 会隐式包一层success(),于是filter一旦失败(checkout 抖动、dorny/paths-filter故障、10 分钟超时),七个 job 会被一起跳过 —— 而 skipped 在分支保护里算通过。结果是零测试运行、全绿可合、没有任何红色信号。七处统一改成「只有过滤器明确说 false 才跳」,每个 job 保留自己的输出名:
testcore!cancelled() && needs.filter.outputs.core != 'false'temporal-conformancecoredogfoodcoredogfood-verifycorebuild-corecorebuild-docsdocs... needs.filter.outputs.docs != 'false'console-pinconsole... needs.filter.outputs.console != 'false'这与
filter自身 outputs 上|| 'true'的取舍完全一致 —— 存疑就全跑。作者意图本来就写在代码里了,只是那半只覆盖「输出值缺失」,没覆盖「job 整体失败」。真值表:只有一个状态改变了行为
filter的每种结局,旧条件 vs 新条件(模拟脚本按 GitHub 文档语义实现,两种「失败 job 的 outputs 是什么」的读法都跑了):outputs.corefilter成功,核心路径有改动'true'filter成功,核心路径没改动'false'filter失败(本 issue)''或'true''''true'只有 S3 变了,其余四个状态逐字节同行为 —— 这是共享基础设施改动该有的爆炸半径。
S3 那一行同时说明了为什么这个写法是稳的:「失败 job 的 outputs」有两种读法(null/空串,或者
|| 'true'照常渲染),新条件在两种读法下都跑('' != 'false'与'true' != 'false'都为真);而旧条件在两种读法下都跳,因为隐式success()压过一切。修法不依赖于这个语义细节的判定结果。!cancelled()只排挤隐式success(),不改变取消语义:真正的 run 级取消(cancel-in-progress)仍然跳过,即 #3668 的 run-lifecycle 论证原样保留。超时的 job 结论是failure而非cancelled,所以 issue 点名的第三种失败模式也被 S3 覆盖。repo 内已有同形状的生产先例:
release.yml:342的dockerjob 正是靠!cancelled() && needs.release.outputs.published == 'true'在上游 job 失败后仍然执行(#4900)。另一半:聚合闸门
issue 自己的分析指出,
case "$result" in success|skipped|cancelled)分不清「路径过滤器说没碰核心代码」和「过滤器自己炸了」。只修if:会把这半个洞留着。test-gate与dogfood-gate因此把filter纳入needs,并在某条腿是skipped而filter未成功时判红。这条分支在新if:下不可达 —— 留作不变量的常驻断言,因为它守的失败模式是静默的,而那七行if:距离被人手滑改回去只有一次编辑。cancelled两侧仍然放行,理由与dogfood-gate里已有的那段一致。build-core/build-docs/console-pin/temporal-conformance背后没有聚合闸门(它们自己的 name 就是必需检查),所以对这四个来说if:就是全部的修复。验证
CI 配置没法靠跑本地测试套件证明,所以做了三件能真做的:
1.
actionlint1.7.7,改前改后都零发现绿色不是空转 —— 反向对照:故意把
docs拼成doc、把test-gate的needs去掉filter,actionlint 立刻两条都点名:也就是说它确实在逐条类型检查这些表达式:七处
if:引用的输出名真的存在于filter的 outputs 上,两个闸门的needs.filter.result真的有needs:撑着。2. YAML 解析 + job 图
python3 yaml.safe_load通过,10 个 job 全部还在,needs/if逐条 dump 核对,无环。3. 两个闸门的 shell 脚本按真实结果状态跑遍
从 YAML 里抽出
run:原文,替换表达式后交给真 bash 执行(不是重写一份等价逻辑)。test-gate跑了 4×4 全交叉:dogfood-gate同样,并确认它不会短路 —— 一腿 pass 一腿 error 时两行都打出来且整体 exit 1。其余门禁:
check-nul-bytesOK(5379 个文件),check-changeset-fixedOK,check-changeset-no-majorexit 0,控制字符自查grep -naP零命中。changeset
CI-only,不发布任何包,所以用了仓库既有的空 frontmatter 写法(与
ci-cache-tier1-optimizations.md/agents-releases-freeze-merge-queue.md同款)—— 这是Check Changeset明确认可的「本 PR 不发布任何东西」声明,门禁照常绿。并发核查
Operational note 8:重跑
git log --oneline origin/main -- .github/workflows/ci.yml,最新仍是认领时那个b8503183c(与本单无关),期间无人改动ci.yml,不存在静默 revert 的风险。派生
#4928正文提到的「静态扫出来」那条规则拆成了 #5343 单独立单,没有塞进本 PR:落地它需要处理publish-smoke.yml:98这个唯一存量违规,而那里的resolvejob 没有|| 'true'兜底、ref也由它算出,「存疑就全跑」会导致 checkout 落到错的 ref 跑一趟 45 分钟的 job —— 属于那个 workflow 自己的取舍,不该由本 PR 顺手拍板。#5343 里写了完整审计表和验收条件(含「输入缺失必须大声失败」的 #4690 约束)。🤖 Generated with Claude Code
https://claude.ai/code/session_01FTszibd6C8sUCCZnM4VcrL
Generated by Claude Code