pitfalls:补记「同一条命令的口径差异」——CI 用提交范围比较 git diff --check
Project CI / Repository checks (pull_request) Successful in 3m17s
Project CI / Frontend tests (pull_request) Successful in 3m55s
Project CI / Backend tests (pull_request) Successful in 6m17s
Project CI / Native shell tests (pull_request) Successful in 17m11s

- docs/project-memory/shared-memory/pitfalls.md:在「只跑门禁子集 → 同一天两次 CI 红」条目的「原因」里补第三类成因
- 口径差异:CI 跑 git diff --check "${base_ref}"..."${head_ref}"(提交范围),本地裸跑 git diff --check(工作树 vs 索引)在干净树上恒为空,所以本地绿不能证明 CI 这一跳会过
- 实例:pitfalls.md 末尾多出的空行(f16aa440b 引入),因前两次推送分别停在 check:git-hooks 与 check:rustfmt 而一直未被这一跳检查
This commit is contained in:
2026-09-12 02:07:49 +08:00
parent a5c7938d05
commit 90177308e0
@@ -5350,7 +5350,7 @@
## 2026-09-12 只跑门禁子集 → 同一天两次 CI 红:本地必须按 npm run lint 的完整链路跑齐
- **现象**:同一天两次 Repository checks 红,都不是代码写错,而是「本地只跑了门禁的一个子集」:① `dd7cf401a` 给 lint-staged 加了 `*.rs` 键,**本地从未跑过 `check:git-hooks`**(它是 `npm run lint` 链里的一环),而 `scripts/git-hooks.test.mjs` 用 `assert.deepEqual` 钉住 lint-staged 的**整份配置形状** → CI 以 `deepStrictEqual` 失败;② 本批更早还因 `check:rustfmt` 红过一次(`15660a98b` 修掉 8 处格式偏差),成因是 pre-commit 的 lint-staged 当时只覆盖 `*.{js,mjs,cjs,ts,tsx}`,Rust 格式在本地没有任何守卫。
- **原因**:`npm run lint` 是一条 `&&` 长链 —— `check:encoding && check:npm-workspaces && check:git-hooks && check:rustfmt && check:spacetime-schema && check:production-ops && check:preview-deployer && check:maintenance-page && lint:eslint && typecheck`(其中 `check:git-hooks` = `node --test scripts/git-hooks.test.mjs`)。**只要中间某一步失败,它之后的步骤根本不会执行**,于是「跑到第 N 步就以为本地绿了」;而 CI 的 Repository checks 跑的是 `scripts/check-repository-ci.sh` → `SPACETIME_SCHEMA_BASE_REF=… npm run lint`(外加 `appSurface.test.ts`、`npm run build`、基线到候选提交的 `git diff --check`)。更隐蔽的是:本地若因**环境原因**在中间断掉(如 Windows 上 `check:git-hooks` 第 2 个用例的 EBUSY),**后面那些本来能通过的步骤也从未被验证过**。
- **原因**:`npm run lint` 是一条 `&&` 长链 —— `check:encoding && check:npm-workspaces && check:git-hooks && check:rustfmt && check:spacetime-schema && check:production-ops && check:preview-deployer && check:maintenance-page && lint:eslint && typecheck`(其中 `check:git-hooks` = `node --test scripts/git-hooks.test.mjs`)。**只要中间某一步失败,它之后的步骤根本不会执行**,于是「跑到第 N 步就以为本地绿了」;而 CI 的 Repository checks 跑的是 `scripts/check-repository-ci.sh` → `SPACETIME_SCHEMA_BASE_REF=… npm run lint`(外加 `appSurface.test.ts`、`npm run build`、基线到候选提交的 `git diff --check`)。更隐蔽的是:本地若因**环境原因**在中间断掉(如 Windows 上 `check:git-hooks` 第 2 个用例的 EBUSY),**后面那些本来能通过的步骤也从未被验证过**。③ 还有一类同样隐蔽的是**同一条命令的口径差异**:CI 的 `git diff --check` 跑的是**提交范围** `git diff --check "${base_ref}"..."${head_ref}"`(base 取 PR 的 base sha,CI 日志里是 `[repository-ci] base=…`),而本地裸跑 `git diff --check`(工作树 vs 索引)在干净树上**恒为空**——于是「本地 `git diff --check` 干净、exit 0」根本不能证明 CI 这一跳会过:`pitfalls.md` 末尾多出的那一个空行(`f16aa440b`)就是这样漏掉的,而且它在前两次推送里都因为链条分别停在 `check:git-hooks` / `check:rustfmt` 而从未被这一跳检查过。本地要同口径复现与自检,必须用 `git diff --check <base>..HEAD`,不要裸跑 `git diff --check`。
- **处理**:push 前按链路逐条跑,不要只跑子集;`&&` 链在某一步失败时,**必须把失败步之后的每一步单独再跑一遍**(上列 10 步 + `git diff --check`),逐条记 exit code(取 exit code 不要接管道)。凡改动碰到 `package.json` / `.husky/` / `scripts/` 下的门禁资产,**`check:git-hooks` 是必跑项**——它钉住 hook 与 lint-staged 配置的形状,增删键都会让它以 `deepStrictEqual` 失败。
- **验证**:本次 `check:git-hooks` 因本机 Windows EBUSY 中断后,把其后各步单独跑齐:`check:spacetime-schema` / `check:production-ops` / `check:preview-deployer` / `check:maintenance-page` / `lint:eslint` / `typecheck` / `check:encoding` 全部 `exit 0` —— 即「因为前一步红而没跑到」的部分本来是全绿的;只有 `check:rustfmt` 红,且红在别人在途的 `.rs`(`assets.rs:519` 落在其未提交 hunk `+470,62`、`commands.rs:3079` 落在其未提交 hunk `+3068,58`)。变异验证:把 `*.rs` 从 `package.json` 摘掉 → `check:git-hooks` 第 1 个用例以同样的 `deepStrictEqual` operator 变红(`# fail 2`);还原(`package.json` SHA256 与改前一致)后该用例回 `ok`。
- **关联**:`package.json` 的 `lint` / `check:git-hooks` / `check:rustfmt`、`scripts/check-repository-ci.sh`、`scripts/git-hooks.test.mjs`、`.husky/pre-commit`、提交 `dd7cf401a` / `2a7bfadd7` / `15660a98b`。