Skip to content

CI: TypeScript Type Checkfetch-depth: 0,authorable-surface 删除门把「main 新增的键」误判成「本 PR 删了键」 #6359

Description

@os-zhuang

现象

2026-08-07 15:02,PR #6356(只改 driver-memory / driver-mongodb 的类型签名,一行 packages/spec 都没碰)在 TypeScript Type Check 上红:

❌ 1 authorable baseline line(s) were deleted without proof (#4650):
   - ui/BulkActionDef:requiredPermissions — def reachable from the metadata-type roots;
     the entry at d8e8d9cbc892 was LIVE (never tombstoned).

同一 job 在上面几行已经自陈了原因:

(shallow history — using origin/main tip d8e8d9cbc892 as the baseline anchor)

归因(已坐实,非猜测)

d8e8d9cbcmain 当前 tip,提交标题逐字是 feat(spec): declare requiredPermissions on BulkActionDefSchema (#6257) (#6332) —— 它新增了这个 authorable key。PR #6356 的分叉点是 4d552af3f,落后 main 三个提交:

d8e8d9cbc feat(spec): declare requiredPermissions on BulkActionDefSchema (#6257) (#6332)
cfb549db8 feat(runtime): standalone stack dispatches mysql:// ... (#6344)
fb363b20e docs(lint): 按实测改正 `normalized` 输入层的三条依据 ... (#6340)
4d552af3f ← #6356 / #6355 的 base

于是「main 上新增的键」在分支侧看起来就是「本 PR 删掉的键」。方向恰好反了。

根因是一行 CI 配置

resolveSurfaceBase()packages/spec/scripts/build-schemas.ts:1024-1031)的逻辑本身是对的,注释也写明了意图:

// Merge base, so a branch behind origin/main is compared against what it
// FORKED from (keys added on main since then are not "deleted" here). In a
// shallow clone there is no walkable ancestry — fall back to the tip, which
// on a PR's synthetic merge commit is the merge base anyway.
const mergeBase = git('merge-base', 'HEAD', tip);
const rev = mergeBase.status === 0 ? mergeBase.stdout.trim() : tip;

括号里那句假设正是失效的一环。而问题在于 fallback 不是罕见降级,它是这个 job 的常态路径

.github/workflows/lint.yml:378typecheck job 用的是 actions/checkout@v7 默认 fetch-depth: 1。加上门自己那句 git fetch --quiet --depth=1merge-base HEAD origin/main 在这个 job 里永远走不通 —— 每一次运行都落到 tip 分支。

而 tip == merge base 这个假设只在「合并 ref 是对着 main 当前 tip 生成的」时成立。合并 ref 是 PR 打开/更新时生成、随 main 前进而变陈旧的;本例中合并 ref 建于 4d552af3f--depth=1 却抓到了 d8e8d9cbc,两者不等,门就看见了幽灵删除。

同一个文件里的兄弟 job 已经踩过并修好了同一个坑.github/workflows/lint.yml:32-38,ESLint job):

      - name: Checkout repository
        uses: actions/checkout@v7
        with:
          # The slot-lookup ratchet compares the baseline against its state at
          # the merge base with main — the only way to see a file being ADDED
          # to the grandfather list. A shallow clone has no merge base, and the
          # check would degrade to "not verified" on every run.
          fetch-depth: 0

typecheck job 需要的是同一句话,只是失效方式更糟:ESLint 那道门 shallow 时降级为不校验,这道门 shallow 时降级为误报红

影响面

任何分叉点早于「某个新增 authorable key 的提交」、且此后跑过 TypeScript Type Check 的开放 PR,都会在一个自己从未碰过的文件上红。#6332 于本日 15:00 前后合入,此刻 #6356#6355 均命中。这是一个假红发生器:它按 main 的合并节奏周期性地扫过所有在飞 PR,而报错文案(「删除了 authorable 键」「未经证明」)指向的是一个严重的规范违规,读起来完全不像环境问题 —— 排查成本远高于修复成本。

建议修法

  1. 一行修复:给 lint.ymltypecheck job 加 fetch-depth: 0,注释比照兄弟 job 写明「这道门要走 merge base」。
  2. 把假设变成断言(建议一并做):resolveSurfaceBase() 的 tip fallback 目前是静默正确性降级。既然 tip ≠ merge base 会产生假红而非假绿,这一路不该悄悄执行 —— 要么在 rev !== tip 无法判定时明确拒绝把「删除」判成违规(只报 ℹ️ 未验证),要么把「拿不到 merge base」直接变成对 CI 配置的显式报错。这样下次某个 job 忘了 fetch-depth: 0,红的是配置本身,而不是无辜 PR 的规范合规性。

现场处置

两个 PR 均已 update branch 合入 main 重跑,问题消失 —— 但那是绕开,不是修复;本单记录的是 CI 配置本身。

按 PD#10 只记录不修,未认领。

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions