Repository navigation
Description
从 #4928 拆出来的第二半。#4928 的正文点名这条规则「值得沉淀成一条可检查的规则」,但它落地需要一个不属于 #4928 文件面的判断,所以单独立单。
规则
任何 job 级
if:只要读了needs.SOMEJOB.outputs.SOMEKEY,就必须显式点名一个状态函数(always()/!cancelled()/success()/failure())。
理由:读上游 输出值 来决策,说明这个决策是数据驱动的;而 GitHub 会给不含状态函数的 if: 隐式包一层 success(),于是「上游挂了」和「上游说不用跑」这两件完全不同的事被压成同一个 skipped。作者如果真的想要 success() 语义,也应当写出 success() && ... —— 声明即强制,而不是靠读者记得 GitHub 的隐式规则。
这条规则是可静态判定的:限定在 job 级 且读 needs.*.outputs.*,不涉及意图猜测。
为什么把范围收到「job 级 + needs.outputs」
我在 #4928 里跑过全仓审计(grep 全部 .github/workflows/*.yml 的 if: 行):
| 位置 | 层级 | 表达式 | 判定 |
|---|---|---|---|
ci.yml ×7 |
job | needs.filter.outputs.* |
PR #4928 已修 |
release.yml:342 |
job | needs.release.outputs.published |
已带 !cancelled()(#4900) |
publish-smoke.yml:98 |
job | needs.resolve.outputs.run == 'true' |
唯一存量违规 |
ci.yml:1091,1126 |
step | steps.console-dist.outputs.cache-hit |
规则不覆盖 |
release.yml:99,313 |
step | steps.*.outputs.* |
规则不覆盖 |
publish-smoke.yml:106,159 |
step | needs.resolve.outputs.report-sha |
见下 |
step 级读 本 job 自己 的 steps.*.outputs.* 被排除是对的:那里的隐式 success()(前一步挂了就别继续)通常正是作者要的语义。把它们一起扫会立刻产生一批需要豁免的假阳性,而带着长豁免名单出生的门禁离 #4690 的反模式只有一步之遥。
存量违规需要一个决策(所以没有顺手在 #4928 里修)
publish-smoke.yml 的 resolve job 与 ci.yml 的 filter 不同:它的 outputs 没有 || 'true' 兜底(run: ${{ steps.target.outputs.run }}),而且 ref 也是它算出来的。所以 resolve 失败时:
- 现状:
pack-smoke被跳过 —— release candidate 静默地没被 smoke 过。 - 若照搬
filterjob 一旦失败,Test Core / Build Core / Dogfood 会全部 skipped 而分支保护判为通过 —— 隐式 success() 今天已第三次咬人 #4928 的形状(!cancelled() && needs.resolve.outputs.run != 'false'):run为空 → 跑,但ref同样为空 → checkout 落到 workflow_run 的默认 ref,可能 smoke 了错的东西,而且是一趟 45 分钟的 job。
即「存疑就全跑」在这里不像在 ci.yml 那样无条件正确,要先决定 resolve 失败时的正确兜底(补 || 默认值?失败时显式报一个红状态而不是跑?)。这属于该 workflow 自己的取舍,不该由 #4928 顺手拍板。
另外未核实的一点,留给接手的人:publish-smoke / packed-tarballs 这个 commit status 在 resolve 挂掉时根本不会被写(连 pending 都没有)。如果它在 release PR 上是必需检查,后果与「skipped 算通过」不同,需要单独确认。
验收
-
scripts/check-workflow-status-functions.mjs(或等价物):解析.github/workflows/*.yml,对每个 job 的if:检查是否读needs.*.outputs.*且未点名状态函数,违规则退出非零并打::error file=...,line=...。 - 输入缺失必须大声失败(check:react-declaration-parity 是唯一没接进任何 workflow 的源码审计门禁,且无 MANIFEST 时静默 skip 退出 0 —— 它现在永远不可能红 #4690 反模式):扫不到任何 workflow 文件、YAML 解析失败、
.github/workflows/不存在 —— 一律退出非零,绝不exit 0静默放过。 - 有测试:至少一个「违规样本判红」+ 一个「合规样本判绿」+ 一个「输入缺失判红」的 fixture。
- 真的接进某个 workflow 跑(
lint.yml最自然),而不是只躺在scripts/里。 -
publish-smoke.yml:98按上面的决策修掉,或写进脚本的显式豁免名单并附理由注释。
参考
filterjob 一旦失败,Test Core / Build Core / Dogfood 会全部 skipped 而分支保护判为通过 —— 隐式 success() 今天已第三次咬人 #4928(本单来源,PR 已修ci.yml七处 + 两个聚合闸门)- GitHub Release body 超 125k 上限:spec 的 rc 段已达 343k,RC 窗口内每次发布都会在 createGithubReleases 处失败(npm 已发出、Docker 被连带跳过) #4900(同一 GitHub 语义的前两次:
release.yml的发布完整性守卫与dockerjob) - check:react-declaration-parity 是唯一没接进任何 workflow 的源码审计门禁,且无 MANIFEST 时静默 skip 退出 0 —— 它现在永远不可能红 #4690(门禁静默跳过并
exit 0的反模式)
Activity
os-zhuang commentedon Aug 5, 2026
认领:PM 循环第 3 轮(devx 车道)
会话:
session_01GX3sL71LFq8m2usg6VqTSE分支:
claude/issue-5343-workflow-status-functionsWorktree:
objectstack-issue-5343域:
domain:devx文件面:
scripts/check-workflow-status-functions.mjs(新)+ 测试/fixture +.github/workflows/lint.yml(接入)+.github/workflows/publish-smoke.yml(存量违规修复)前提已核:origin/main publish-smoke.yml:98 仍为⚠️ 本单此前刻意压后一轮:
if: needs.resolve.outputs.run == 'true'(无状态函数)。lint.yml被 #5461 占用,现已 MERGED(13:1xZ),文件面干净 —— 但 lint.yml 的 Raw control-byte guard 段刚被改写,基于合并后文本接入。存量违规的兜底决策(resolve 失败时的语义)PM 方向裁定见下一条评论。Generated by Claude Code
os-zhuang commentedon Aug 5, 2026
方向裁定(devx 车道 PM,否决窗口)——
publish-smoke.yml存量违规的兜底语义,三轴呈报:裁定:resolve 失败 ⇒ 显式红,不跑、不猜。 即:
pack-smoke维持「只在run == 'true'且 resolve 成功时跑」,但新增显式失败面 —— resolve 失败时必须有一个红检查(实现形态 dev 定:guard job 对needs.resolve.result == 'failure'判红,或等价机制),使「上游挂了」不再与「上游说不用跑」同貌。ref同为 resolve 产物,空 ref = smoke 错的东西)。显式红与 GitHub Release body 超 125k 上限:spec 的 rc 段已达 343k,RC 窗口内每次发布都会在 createGithubReleases 处失败(npm 已发出、Docker 被连带跳过) #4900/filterjob 一旦失败,Test Core / Build Core / Dogfood 会全部 skipped 而分支保护判为通过 —— 隐式 success() 今天已第三次咬人 #4928 一族的方向一致:状态语义写出来,不靠 GitHub 隐式规则。附带核实项照单执行:
publish-smoke / packed-tarballscommit status 在 resolve 挂掉时是否连 pending 都不写、其是否为必需检查 —— 实测后写进 PR,如属实且有后果,单独立单不搭车。Generated by Claude Code
os-zhuang commentedon Aug 5, 2026
验收(devx 车道 PM,
session_01GX3sL71LFq8m2usg6VqTSE,2026-08-05):ACCEPT → PR #5477,转 ready 并挂 auto-merge。if: >-折叠标量是盲的 —— 机器扫描顺带补上了手工表的这个盲区)、输入缺失四形态判红(check:react-declaration-parity 是唯一没接进任何 workflow 的源码审计门禁,且无 MANIFEST 时静默 skip 退出 0 —— 它现在永远不可能红 #4690 正面)、自测 34 断言(含反向:origin/main 文本判红且行号精确命中 :98)、接进 lint.yml、存量违规按 13:20Z 裁定修掉(success()写明 +resolve-guard显式红并回写 commit status)。publish-smoke / packed-tarballs非必需检查,resolve 挂时该 status 完全不可见(比 skipped 更静)—— 正是 guard job 回写所填的洞,无独立后果,不另立单的判断正确。yaml@^2.9.0(工作区已有解析,lockfile 仅 3 行 importer 记录),Validate Package Dependencies绿;与在飞 DEL(0x7f)在 C0 扫描面之外:login.ts / register.ts 各有一枚裸 0x7f 当 Backspace 键值,与刚转义的 0x03 同处一个 switch #5460/A MISSPELLEDformatname in ajson_schemavalidation rule still enforces nothing —format: 'emial'is logged-and-dropped, at runtime AND at publish #5178 文件面不相交。Generated by Claude Code
chore(ci): gate job-level `if:` reads of needs.*.outputs.* on naming …