Skip to content

finding: lint.yml 的 hook self-test 步骤把依赖面写死在注释里,而该步骤是 glob 发现式的 —— 新 matrix 带来的依赖漂移静默且无机器读物 #14141

Description

@claude

事实(2026-09-01,实施 governed-enqueue hook 时撞到,只记录不认领)

.github/workflows/lint.ymlClaude hook guard self-tests 步骤,注释里写死了一条热力学声明(原文):

Hermetic by construction, and measured that way: each matrix builds its own git repo and linked worktree under $TMPDIR … Needs jq and git and nothing else — no pnpm, no node, no build, no network … ~2 s measured.

该步骤是 glob 发现式的(find .claude/hooks -type f -name '*.selftest.sh'),这一点是刻意设计、且是对的。但发现式与「依赖面写死在注释里」两者组合出一个漂移通道:新加进来的 matrix 无需修改这段注释就会被跑,于是注释描述的依赖面与实际跑的依赖面可以任意分叉,而没有任何机器读物在看这件事(该步骤自己的注释已经承认:check:step-collectors 看不到这个块,「the collector shape here is held by review, not by that gate」)。

已经发生的实例

guard-governed-enqueue.selftest.sh(本轮落地,PR #14140)需要 node:它刻意不 stub 两个判据 —— 受管面判据是 check-governed-merges.mjs --test,钉头批准判据是 check-governed-queue-guard.mjspinnedApprovalVerdict —— 因为「hook 不自己判、只转述单源」正是该 matrix 要钉住的性质,stub 掉就什么都没测。其中一条 case 还会在依赖装好的环境里真的跑一次生成器 --check(纯再生成豁免的 lift 腿)。

⇒ 注释里的 no pnpm, no node, no build 三项中,no node 现在是假的;no pnpm / no build 在 lift 腿上也不再成立。~2 s measured 同样过期。

⚠️不会让 CI 变红:该步骤住在 Lint & Repo Gates job 里,那里 node 与依赖都在。所以漂移是静默的 —— 这正是它值得立卡的原因,而不是不值得。

危害

注释是这条依赖面的唯一记录。它被信任的场景恰好是有人要动这个步骤的时候:把它挪到更瘦的 job、给它加 container:、按「反正只要 jq 和 git」削减 setup 步骤 —— 每一种都会把一批 matrix 变成 127/1,而做决定的人读到的是一份已经不真的清单。同一份注释还在教下一个 matrix 作者「你可以假设没有 node」,而实际约束已经不是这个。

可选修法(不预判,留给分诊)

  • A(最小):更新注释,把依赖面写成「jq + git,加上 matrix 自己声明的任何东西」,并要求新 matrix 在自己的头注里声明依赖。散文对散文,零机器保障。
  • B(机器化):让步骤在跑之前读取每个 matrix 头注里的依赖声明行并据此 preflight(缺失即红),把「依赖面」从注释升格为可读数据。与该步骤已有的「发现式而非清单式」精神一致。
  • C:给每个 matrix 一个 --deps 自述模式,步骤先收集再校验。

⛔ 本卡不认领、不预设选项。

关联

查重

同会话读了 24 张 open finding 卡与 100 张 open 卡,标题层无 hook self-test / hermetic / lint.yml 依赖面相关命中。⛔ 无重复。


Generated by Claude Code

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions