Skip to content

静态扫描:job 级 if: 读 needs.*.outputs.* 必须显式带状态函数(#4928 沉淀的规则 + publish-smoke.yml 唯一存量违规) #5343

Closed

Description

@os-zhuang

从 #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 失败时:

即「存疑就全跑」在这里不像在 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 按上面的决策修掉,或写进脚本的显式豁免名单并附理由注释。

参考

Activity

  1. added
    toolingFix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface
    pm:queueReady for the PM dispatch loop
    domain:devxDomain lane — seat card indexed by label:pm:seat
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Aug 5, 2026
  2. self-assigned this
    on Aug 5, 2026
  3. os-zhuang commented on Aug 5, 2026

    @os-zhuang
    ContributorAuthor

    认领:PM 循环第 3 轮(devx 车道)
    会话:session_01GX3sL71LFq8m2usg6VqTSE
    分支:claude/issue-5343-workflow-status-functions
    Worktree: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

  4. os-zhuang commented on Aug 5, 2026

    @os-zhuang
    ContributorAuthor

    方向裁定(devx 车道 PM,否决窗口)—— publish-smoke.yml 存量违规的兜底语义,三轴呈报:

    裁定:resolve 失败 ⇒ 显式红,不跑、不猜。 即:pack-smoke 维持「只在 run == 'true' 且 resolve 成功时跑」,但新增显式失败面 —— resolve 失败时必须有一个红检查(实现形态 dev 定:guard job 对 needs.resolve.result == 'failure' 判红,或等价机制),使「上游挂了」不再与「上游说不用跑」同貌。

    附带核实项照单执行:publish-smoke / packed-tarballs commit status 在 resolve 挂掉时是否连 pending 都不写、其是否为必需检查 —— 实测后写进 PR,如属实且有后果,单独立单不搭车。


    Generated by Claude Code

  5. os-zhuang commented on Aug 5, 2026

    @os-zhuang
    ContributorAuthor

    验收(devx 车道 PM,session_01GX3sL71LFq8m2usg6VqTSE,2026-08-05):ACCEPT → PR #5477,转 ready 并挂 auto-merge。


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

ci/cddomain:devxDomain lane — seat card indexed by label:pm:seatpm:dispatchedDispatched to a dev agent by /pm-dispatchpm:queueReady for the PM dispatch looptoolingFix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions