You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 8210894
Browse filesBrowse the repository at this point in the historyBrowse files
ci: check out Type Check UI at depth 1 and give every history fetch a budget sized to its job (#31644)
<!-- claude-projects-attribution: {"github_login":"zhuangjianguo"} -->
_Requested by **Steedos** · [project
thread](https://claude.ai/code/project/chan_018ETKaNwMZCNRYD7D8Q9Ubb?thread=cmsg_018ETKaNwMZCNRYD7D8Q9Ubb2CZ7ohopLMiBhAucfDgqvA)_
domain:devx#2 · zhuangjianguo · session_01QsR2jRSntnsNKYKPXp9kFG
Refs #22034
Clause-②: no
Metadata-protocol: none
## 维护者速读
- 改了什么:根 `ci.yml` 的 `Type Check UI`(`type-check-ui`)也不再用 `fetch-depth:
0`,改为深度 1 检出,加上与 Test Core / Test UI 逐字相同的一步"取当前提交的完整祖先 + main(及 PR
目标分支)",不取 tag 和其它分支;四个 job 的这一步各自加超时,按各自 job 墙钟留出的余量定:test-plan 8
分钟、test-ui 14、test 20、type-check-ui 20;新增一条测试钉住"四个 job 都是深度 1 + 紧跟取历史步骤
+ 预算不低于 8 分钟且小于 job 墙钟",两条原本钉 `fetch-depth: 0` 的测试改钉取历史步骤。
- 为什么改:#22034 第二步的第二个 PR,收尾第一个 PR(#31429)留下的四件事。注意实测:健康日新路径并不更快,三个 job
合并看中位数约慢 5 秒(新 62 秒 vs 旧 57 秒),其中 test-plan 慢 14 秒;收益在故障尾部——去掉的 1.5k 分支和
1.7 万 tag 正是 11-03 事故里慢 5.6–6.5 倍、10-13 三次传输失败的那部分负载。两组分布及 n 见下方
Acceptance notes。
- 风险与回滚:风险是 `Type Check UI` 的读者拿不到历史;已逐个核对四个读者(lockfile 告警、i18n 漂移、受影响
type-check 范围、spec 构建)都由这一步满足。取历史三次失败或超过该 job 的预算会让该步变红,而不是静默。回滚即还原本 PR。
- 先看:`.github/workflows/ci.yml` 的 `type-check-ui` 检出与新步骤;`test` job
注释里新加的 FETCH BUDGET
段;`ui/scripts/__tests__/select-affected-tests.test.ts` 末尾的新 describe。
- 两分钟验证:打开本 PR 的 CI,看 `Type Check UI` 的 "Fetch this commit's history and
the base branch" 日志打印 `shallow=false, no tags`,且该 job 整体为绿。
- 需要你做的:合并本 PR(受管 `.github/workflows/**`,Tier H,须维护者手合)。
## What changes
Five files; only `.github/workflows/ci.yml` is a workflow.
1. **`type-check-ui` checks out at depth 1** and runs the fetch step
right after the checkout. The `with:` / `fetch-depth: 0` block and its
comment are gone. The step is identical, but for its budget, to the
three copies #31429 added in `test-plan`, `test` and `test-ui`: `git
fetch --no-tags --unshallow origin HEAD_SHA
+refs/heads/main:refs/remotes/origin/main`, plus the PR's base branch
when it is not `main`, three attempts, then a red step. Its comment
names this job's readers.
2. **Each fetch step takes its own `timeout-minutes`, sized inside its
job's wall:** `test-plan` 8, `test-ui` 14, `test` 20, `type-check-ui`
20. Besides the budget, the four copies change in one line: the shared
`::error::` now reads "This job's history readers need it, so this job
stops here rather than reading from a shallow clone". The old text named
the package selection, which is not the reader in `test-ui` or
`type-check-ui`. `test`'s comment gains a FETCH BUDGET paragraph with
the sizing (below). The other copies' comments say "verbatim but for its
budget".
3. **Pins.**
- New `describe` in
`ui/scripts/__tests__/select-affected-tests.test.ts`. It runs one case
per job (`test-plan`, `test`, `test-ui`, `type-check-ui`), reading the
parsed YAML. Each case checks that the `actions/checkout` step sets no
`fetch-depth`, that the next step is the fetch step, that its budget is
a number of at least 8 and under the job's own `timeout-minutes`, and
that the job's first history reader comes after it. A fifth case asserts
the four fetch steps are deep-equal once `timeout-minutes` is removed.
- `check-i18n-en-drift.test.ts` and `check-lockfile-integrity.test.ts`
asserted `fetch-depth: 0` on `type-check-ui`. They now assert the fetch
step sits before their gate's step.
4. **Prose.**
- `ui/scripts/check-i18n-en-drift.mjs`: the shallow-clone refusal said
"In CI, give the checkout `fetch-depth: 0`". It now says to fetch HEAD's
history and the base branch after the checkout.
- In `ci.yml`, two comments that cited `fetch-depth: 0` as the source of
`refs/remotes/origin/*` and of the merge base now name the fetch step.
⛔ Not in this PR: any other line of the fetch step,
`select-shard-packages.sh`, any other job or workflow, and
`ui/.github/**`.
## Why
Card #22034, step 2, second PR, released by the maintainer's 「以上都同意」
(recorded 6718334937). It closes what the first PR's hand-off
(6723904729) and contract review 6718987786 ③ left: the same fetch step
for `type-check-ui`, a depth-1 pin, a sized fetch budget, and the stale
`fetch-depth: 0` prose. The step-1 reading is 6724659023. The seat's
rulings are on the amended claim.
## Tests
- `pnpm exec vitest run` (from `ui/`) on
`select-affected-tests.test.ts`, `check-i18n-en-drift.test.ts`,
`check-lockfile-integrity.test.ts` and
`select-affected-tests-wiring.test.ts`: 4 files, 148 tests passed on
`d69b4e47` (`@objectstack/spec` built first; the drift test imports it).
- **Base red:** the same three pin files against `main`'s `ci.yml`
(`dced781f`, restored into the tree, then `git restore --source=HEAD`):
7 failed. They are the four per-job cases, the deep-equal case, and the
two moved pins.
- **Ablations** (`node scripts/ablation-replace.mjs`; each restore left
the blob equal to HEAD and `git diff HEAD` empty):
- On `6d8684a5`, re-adding `with: fetch-depth: 0` to `type-check-ui`'s
checkout landed (anchor 1 → 0) and failed the `type-check-ui` case:
"type-check-ui's checkout sets fetch-depth: expected +0 to be undefined"
(1 failed, 55 passed).
- On `d69b4e47`, raising `test-plan`'s fetch budget to its 10-minute
wall landed and failed the `test-plan` case: "expected 10 to be less
than 10" (1 failed, 55 passed).
- On `d69b4e47`, adding one `env` key to the `test-ui` copy only landed
and failed "the fetch step is one step, the same in all four jobs but
for its budget" (1 failed, 55 passed).
## Acceptance notes
**The cost, plainly.** On healthy runs the depth-1 path is not faster:
pooled over the three jobs it is about 5 s slower at the median, and
`test-plan` is 14 s slower. The gain is the incident tail: the dropped
1.5k heads and 17k tags. Those slowed 5.6-6.5x in the 2026-11-03
incident, against about 3x for shallow fetches, and failed three times
on transport on 2026-10-13. No incident fell in either window, so this
reading does not measure that gain.
Both distributions come from the jobs API, `CI` on `main` (push) and
`merge_group`. Times are in seconds, at 1 s grain.
| Window | Jobs (`test-plan` + `test` + `test-ui`) | n | p50 | p90 | p99
| max |
|---|---|---|---|---|---|---|
| Before #31429: "Checkout repository" with `fetch-depth: 0`, 178 runs,
2026-11-08 01:27Z–16:00Z | all three | 2504 | 57 | 64 | 87 | 143 |
| After: checkout + fetch step, 22 runs (8 push, 14 queue),
16:00Z–17:22Z | all three | 277 | 62 | 79 | 99 | 107 |
| After, fetch step alone | all three | 277 | 50 | 57 | 72 | 85 |
- **Per lane, p50 before → after:**
- Plan Test Core shards: 57 → 71 (n 164 / 22).
- Test Core (N/8): 57 → 62 (917 / 111).
- Test UI (N/16): 58 → 60 (1423 / 144).
- **Control:** `Type Check UI`, still on full history in the same runs,
read p50 53 → 51 (n 89 / 9). The two windows saw the same platform.
- **Fetch outcome:** 277 of 277 fetches succeeded. The slowest, 85 s
(job 128790425643), ran one attempt.
- **Why it is not faster:** the fetch pulls HEAD's whole ancestry, about
20.7k commits. On CI that costs about what the old single full-ref clone
did.
- **What `Type Check UI` should expect:** about the same shift. Its
checkout read p50 53 s before; the new path reads about 60 s.
- **Full reading:** 6724659023, with method and per-event splits. The
seat re-reads n at ACCEPT.
**The budgets: 8 / 14 / 20 / 20 minutes, each inside its job's wall.**
- **The rule:** each job gives the fetch what its wall leaves after its
measured checkout and body (the max over 2026-11-08's runs), capped at
20 minutes. lint.yml's 25-minute CHECKOUT BUDGET is the same reading for
its full-history checkouts.
| Job | Wall | Checkout max | Body max | Room | Budget |
|---|---|---|---|---|---|
| `test-plan` | 10:00 | 0:24 | 0:37 | 8:59 | **8** |
| `test-ui` | 30:00 | 0:28 | 15:29 | 14:03 | **14** |
| `test` | 60:00 | 0:31 | 24:00 | 35:29 | **20** |
| `type-check-ui` | 40:00 | about 0:30 | 14:23 | about 25 | **20** |
- **Healthy floor:** the smallest budget, 480 s, still holds the fetch's
max 85 s x 5.6 and its p99 72 s x 6.5. Those factors are the 2026-11-03
incident's slowdown of the full-ref fetch at the median. Three healthy
attempts plus the 15 s and 30 s sleeps take 300 s.
- **The incident tail:** on 2026-11-03, full-history checkouts of
1063-1799 s cancelled 19 Test UI shards at their wall. This step pulls
the whole ancestry, so I assume its tail is no shorter; that is
unmeasured. 20 minutes holds the tail's low end (1063 s), not its top.
- **Per job:**
- In `test` and `type-check-ui`, a fetch as slow as the tail's low end
now finishes, where a uniform 8 would have gone red.
- In `test-ui`, neither end of the tail fits beside the body inside the
30-minute wall, so the budget only ends the red sooner and names the
step.
- In `test-plan`, a red plan falls back to all eight shards, and each
shard fetches for itself.
**`type-check-ui`'s readers (inventory rows 5-8 of #31429), re-read on
`main` `467a01f7`, all answered by the same step:**
| Row | Reader | Answered because |
|---|---|---|
| 5 | `check:lockfile-integrity` (PR only, `continue-on-error`): `git
merge-base origin/BASE HEAD`, `git show REV:pnpm-lock.yaml` |
`origin/main` is always fetched, plus `origin/BASE` when the base is
another branch; no partial-clone filter, so the blob is local. The step
precedes it. |
| 6 | `check:i18n-drift`: `merge-base HEAD origin/$GITHUB_BASE_REF`,
then `origin/main` | On PR, `origin/BASE`. On queue and push, the merge
base with `origin/main`, the same commit as under `fetch-depth: 0`. |
| 7 | the affected type-check scope, `turbo --filter=...[BASE]` with the
payload `base.sha` or the queue `base_sha` | Both are ancestors of HEAD,
so both are in the unshallowed ancestry. |
| 8 | spec's build (`build-schemas.ts`) | `merge-base HEAD origin/main`,
as row 2. |
None of the job's 16 other `pnpm --dir ui run` checks, the local modules
they import, or its `tsc` steps runs a history-reading git command.
**Left as found:**
- The `fetch-depth: 0` prescriptions in
`packages/spec/scripts/build-schemas.ts`,
`ui/scripts/check-new-cross-file-line-citations.mjs` and
`ui/scripts/check-changeset-claims.mjs` are the seat's follow-up card,
not this PR.
- `test`'s "NOT `fetch-depth: 0`" comment and the old-refspec
description are history, and stay.
**Serial:** `git merge-tree --write-tree` of `d69b4e47` is clean against
PR #31611's head `95c48c6f` and against `main` `c5361e2c`. `main`'s
#31639 also edits `select-affected-tests.test.ts`; I ran that file and
`select-affected-tests-wiring.test.ts` with the merged contents in the
tree: 78 passed.
## Verification
I derived the gate union with `node scripts/pm/dispatch-gates.mjs
--commands --repo objectstack-ai/objectstack`: 62 families for this
diff, on head `d69b4e47`; the same 62 as on `6d8684a5`. I ran every one
and captured each exit code before any pipe. `--ran` reconciliation: "62
derived famil(ies) accounted for — 62 run, 0 NOT-MEASURED (a DERIVED
zero — all 62 recorded an exit code and none of them is 3)".
- **All 62 exit 0.** Among them:
- `pnpm check:required-contexts`, `check:workflow-status-functions`,
`check:workflow-step-name-quoting`, `check:shard-attestation`,
`check:stall-guard-budget`, `check:stall-guard-headroom`,
`check:aggregator-roster`, `check:ci-filter-parity`,
`check:pm-expected-skips`;
- `node scripts/check-step-collectors.mjs`, `node
scripts/check-self-test-workflow-commands.mjs`, `node
scripts/ci/plan-test-core-shards.mjs --self-test`;
- `pnpm check:dts-closure` (256/256 declaration files across 80
packages) and `pnpm check:dual-build-cjs-loads` (122 entry points; both
read the full build `check:type-check-debt` made);
- `pnpm check:type-check-debt` under `os-verify-lock.sh` (VERDICT
command-exit 0; on `6d8684a5` it also ran the full build the two lines
above read);
- `pnpm check:pm-dispatch-gates` ("2205 cases pass", fast tier);
- `pnpm --dir ui exec vitest run scripts/` under the lock (170 files
passed, 2 skipped; 5014 tests passed);
- `node scripts/check-closing-keyword-parity.mjs` against this body.
- **Outside the derivation:**
- `node scripts/pm/check-governed-merges.mjs --branch HEAD`: exit 3,
"GOVERNED — a human merge is the review record", landing tier H,
`.github/workflows/ci.yml` the one register path, 125 changed lines
(under 3000).
- `node scripts/pm/dispatch-gates.mjs --tier`: exit 0, "no path-derived
mandate".
- The PR-body gates on this body (`check-changeset-no-major --base
origin/main`, `check-adr-0087-registration`,
`check-partof-closing-keyword`): each exit 0.
- `pnpm lint`: the diff touches no package, so there is no scoped
package lint.
- `main` moved 16 commits after the branch point (to `c5361e2c`). Only
#31639 touches a file this PR changes, and it merges cleanly and passes
(Serial above). Following the seat's ask, no new head was pushed for it.
`skip-changeset`: `.github/**` and `ui/scripts/**` ship in no package.
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
(not touched)
- [x] 2. Every name I added uses the casing for its kind. (test case
names only)
- [x] 3. No formatter ran; `pnpm lint` passes. (no package touched)
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset, and both
declaration lines are filled in here and in the changeset. (publishes
nothing: `skip-changeset`)
- [x] 6. `packages/spec` touched: its generated artifacts are
regenerated and committed. (not touched)
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English. (the 维护者速读 block is
the lane's required Chinese summary)
- [x] The *Before Submitting* list in CONTRIBUTING.md is done.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01QsR2jRSntnsNKYKPXp9kFG
---
_Generated by [Claude
Code](https://claude.ai/code/session_01QsR2jRSntnsNKYKPXp9kFG)_
<!-- signed: domain:devx#2 · zhuangjianguo ·
session_01QsR2jRSntnsNKYKPXp9kFG -->
---------
Co-authored-by: Claude <noreply@anthropic.com>
if [ "$(git rev-parse --is-shallow-repository)" = true ]; then deepen=(--unshallow); fi
816
818
if git fetch --no-tags "${deepen[@]}" origin "$head" "${refspecs[@]}"; then break; fi
817
819
if [ "$attempt" = 3 ]; then
818
-
echo "::error::Could not fetch $head's history and ${refspecs[*]} after 3 attempts. The package selection reads that history, so this job stops here rather than selecting from a shallow clone."
820
+
echo "::error::Could not fetch $head's history and ${refspecs[*]} after 3 attempts. This job's history readers need it, so this job stops here rather than reading from a shallow clone."
819
821
exit 1
820
822
fi
821
823
sleep $((attempt * 15))
@@ -1078,7 +1080,29 @@ jobs:
1078
1080
# commit, not a branch tip. Three attempts, like the checkout's own fetch,
1079
1081
# then a red step: a selection read from a shallow clone is the silent
1080
1082
# all-packages answer above, never a fallback this job may take quietly.
1083
+
#
1084
+
# FETCH BUDGET: the one line that differs between the four copies. Each
1085
+
# job gives the fetch what its wall leaves after its measured checkout
1086
+
# and body (the max over 2026-11-08's runs on `main` and the queue),
1087
+
# capped at 20 minutes: `test-plan` 8 (wall 10, body 0:37),
1088
+
# `test-ui` 14 (wall 30, body 15:29), `test` 20 (wall 60, body 24:00),
1089
+
# `type-check-ui` 20 (wall 40, body 14:23). lint.yml's CHECKOUT BUDGET
1090
+
# (25) is the same reading for its full-history checkouts.
1091
+
# - Healthy, over the 22 runs after this step landed: 277 fetches, all
1092
+
# green, p50 50 s, p99 72 s, max 85 s.
1093
+
# The smallest budget, 480 s, still holds max x 5.6 and p99 x 6.5
1094
+
# (the 2026-11-03 incident's slowdown of the full-ref fetch at the
1095
+
# median) and three attempts plus the two sleeps (300 s).
1096
+
# - The 2026-11-03 tail: full-history checkouts of 1063-1799 s
1097
+
# cancelled 19 Test UI shards at their wall. This step pulls the
1098
+
# whole ancestry, so its tail is assumed no shorter. 20 minutes holds
1099
+
# the tail's low end (1063 s), not its top. In `test-ui` neither end
1100
+
# fits beside the body inside the 30-minute wall, so its budget only
1101
+
# ends the red sooner and names the step. A red `test-plan` falls
1102
+
# back to all eight shards, and each fetches for itself.
1103
+
# A fetch that hangs ends red here, named, instead of eating the wall.
1081
1104
- name: Fetch this commit's history and the base branch
if [ "$(git rev-parse --is-shallow-repository)" = true ]; then deepen=(--unshallow); fi
1093
1117
if git fetch --no-tags "${deepen[@]}" origin "$head" "${refspecs[@]}"; then break; fi
1094
1118
if [ "$attempt" = 3 ]; then
1095
-
echo "::error::Could not fetch $head's history and ${refspecs[*]} after 3 attempts. The package selection reads that history, so this job stops here rather than selecting from a shallow clone."
1119
+
echo "::error::Could not fetch $head's history and ${refspecs[*]} after 3 attempts. This job's history readers need it, so this job stops here rather than reading from a shallow clone."
1096
1120
exit 1
1097
1121
fi
1098
1122
sleep $((attempt * 15))
@@ -1178,7 +1202,7 @@ jobs:
1178
1202
# gives this checkout a `ref:`, where parent^1 becomes the PR's
1179
1203
# previous commit. `merge-base` is right under BOTH checkouts.
1180
1204
# - Dropping the variable and letting turbo default to `main`. Measured
1181
-
# in a CI-shaped clone: `fetch-depth: 0` populates
1205
+
# in a CI-shaped clone: the fetch step above populates
1182
1206
# `refs/remotes/origin/*`, NOT local heads, so there is no local `main`
1183
1207
# — and turbo does not error, it silently returns the ENTIRE
1184
1208
# workspace, untouched packages included. Safe, and the whole
@@ -3803,12 +3827,39 @@ jobs:
3803
3827
TURBO_RUN_SUMMARY: 'true'
3804
3828
3805
3829
steps:
3830
+
# Depth 1, then the step below: this job reads HEAD's ancestry and the
3831
+
# base branch, and no tag or other head (#22034).
3806
3832
- name: Checkout repository
3807
3833
uses: actions/checkout@v7
3808
-
with:
3809
-
# The affected scope below asks turbo for the packages changed since
3810
-
# the base commit, which needs the base's history in the clone.
3811
-
fetch-depth: 0
3834
+
3835
+
# The `test` job's step of the same name, verbatim but for its budget
3836
+
# (its comment says why and sizes the budget). Here the readers are check:lockfile-integrity
3837
+
# (`git merge-base origin/<base> HEAD`), check:i18n-drift (the merge base
3838
+
# with origin/<base> or origin/main), the affected type-check scope
3839
+
# (`turbo --filter=...[BASE]`) and spec's build. On a shallow clone the
3840
+
# lockfile alarm finds no merge base and stays green, and the drift check
3841
+
# exits red.
3842
+
- name: Fetch this commit's history and the base branch
if [ "$(git rev-parse --is-shallow-repository)" = true ]; then deepen=(--unshallow); fi
3855
+
if git fetch --no-tags "${deepen[@]}" origin "$head" "${refspecs[@]}"; then break; fi
3856
+
if [ "$attempt" = 3 ]; then
3857
+
echo "::error::Could not fetch $head's history and ${refspecs[*]} after 3 attempts. This job's history readers need it, so this job stops here rather than reading from a shallow clone."
if [ "$(git rev-parse --is-shallow-repository)" = true ]; then deepen=(--unshallow); fi
4128
4181
if git fetch --no-tags "${deepen[@]}" origin "$head" "${refspecs[@]}"; then break; fi
4129
4182
if [ "$attempt" = 3 ]; then
4130
-
echo "::error::Could not fetch $head's history and ${refspecs[*]} after 3 attempts. The package selection reads that history, so this job stops here rather than selecting from a shallow clone."
4183
+
echo "::error::Could not fetch $head's history and ${refspecs[*]} after 3 attempts. This job's history readers need it, so this job stops here rather than reading from a shallow clone."
0 commit comments