Skip to content

ci(ui-merge): the seven @objectstack packages build once per run and the ui jobs replay them; every ui cache save is bounded - #25963

Merged
os-zhuang merged 3 commits into
mainfrom
claude/project-thread-kz44z5
Oct 26, 2026
Merged

os-zhuang merged 3 commits into
mainfrom
claude/project-thread-kz44z5

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Tesla · project thread

Fixes #25869
Part of #25677 (post-cutover CI speed, item 3; rows F1, F2, F11 and F13).

Clause-②: no
Metadata-protocol: none

What changed

.github/workflows/ci.yml, the ui jobs only:

  • New job ui-deps (Build UI Deps). It builds the seven @objectstack packages objectui consumes ONCE per run, with the same turbo run build command the ui jobs use, then prunes .turbo/cache to that build's tasks (scripts/ci/prune-turbo-cache.mjs) and uploads those entries as the artifact ui-deps-turbo-cache (36 tasks, ~27 MB, stored uncompressed because the entries are zstd already, kept one day). Its if: is the union of the four ui jobs' conditions. The build carries Build Core's four TURBO_* lines verbatim, followed by assert-turbo-remote-cache.mjs, so a queue or main run replays a closure that was already built from the remote cache. A pull_request run still gets no remote credentials.
  • The four ui jobs (type-check-ui, test-ui, lint-ui, build-e2e-ui) now need ui-deps and download the artifact into .turbo/cache before their unchanged Build the @objectstack packages objectui consumes step. That step then replays every task, measured locally at 1.2 s for 36 of 36 cached. The download is continue-on-error. Every job's if: is byte-identical and starts with !cancelled(), so a red, cancelled or skipped ui-deps never skips a ui check: with no artifact, the job builds cold exactly as before. Nothing the ui jobs test changed.
  • Why cache entries and not dist/. type-check-ui and lint-ui run further turbo tasks that depend on ^build, and only a cache hit stops turbo rebuilding the seven packages there. Turbo still hashes every task, so an entry that does not match the tree gets rebuilt rather than trusted. turbo run build --dry=json gives the same 36 task hashes with and without the env the jobs differ in (TURBO_RUN_SUMMARY, NODE_OPTIONS, TURBO_CACHE).
  • F1 + F11: every ui cache save is bounded. Each one is a main-phase actions/cache/save with timeout-minutes: 5 and continue-on-error: true, placed after the job's last checking step and guarded on its restore's cache-hit. No ui job uses the combined actions/cache any more. The pnpm store is restored by every ui job and saved by ui-deps alone. test-ui and build-e2e-ui drop their own turbo seed, since the seven-package build is their only turbo run. type-check-ui and lint-ui keep theirs.
  • F2: decided, the root jobs do not run ui/scripts/report-turbo-cache.mjs. The prune before each seed save is objectstack's scripts/ci/prune-turbo-cache.mjs (the same summary-driven prune, fed by job-level TURBO_RUN_SUMMARY). The hit/miss report is assert-turbo-remote-cache.mjs's step-summary table on Build UI Deps. The script retires with its last callers, the nested ui/.github/workflows/ (tooling(ui-merge): governance in-repo — liveness evidence and spec tests stop citing objectui by commit, one sdui-parser, AGENTS.md / pm scripts drop objectui#, ui/AGENTS.md nests, 18 governance workflows merged or retired (parent #25677, 4b/6) #25712); report-turbo-cache.test.ts's retirement note now says so.
  • F13: the ceilings are derived and pinned in a comment above lint-ui, using objectui's rule (smallest round number that is ≥ 3× the slowest successful run and ≥ that run + 15 min) over every successful job since the cutover. Lint UI: n=42, max 558 s, which gives 30 (unchanged). Build & E2E UI: n=51, max 457 s, which gives 25 (was 45; nothing backed the extra 15). ui-deps is 25 by the same rule.
  • Build Core's remote-cache comment lists Build UI Deps among the carriers.

ui/scripts/__tests__/turbo-cache-save-bound.test.ts is restored, retargeted to the root jobs by path through helpers/root-ci.ts. It brings back the six pins #25879 retired, now over all five jobs: main-phase saves, bounded and non-fatal, ceiling at least twice the bound, saves after the last command, the same key with a cache-hit guard, and ceilings not raised. It adds the hand-off pins: needs: ui-deps with an !cancelled() if:, a fail-open download before the build step, and a build command identical to ui-deps's.

⛔ Not touched: the lint-ui gate steps #25902 adds. That PR edits lines next to two of this PR's lint-ui hunks (the Install dependencies step and the steps before Save Turbo cache). Whichever lands second takes a small textual merge. Re-derive Lint UI's ceiling when #25902's gates land, because they add a full ui-package build to that job.

Measured before (the card's "before")

The 51 CI runs that scheduled ui jobs between the cutover (#25825) and 2026-10-26T05:30Z, step and job times taken from the Actions jobs endpoint:

runs seven-package build per ui job runner-min on that step
warm: every ui job replays from its own seed 24 0–2 s 0.3 per run
cold: every ui job builds 18 full-lane + 9 two-lane 86–288 s, median 201 s 33–77 per full-lane run, 7–8.5 per two-lane run
total 51 1191

A cold seed is the common case. The seeds are saved per job on main pushes, so a PR or queue entry whose closure differs from the newest seed's misses in all twenty jobs at once. Examples: queue entry 40271867662 (#25940, ui test files only) 70.2 runner-min; PR run 40266487909 74.0; push 40268844566 62.0.

Expected after (ui-deps builds once): a cold full-lane run spends one build (~4 min) instead of ~70 runner-min. A warm run gains ~1.5 runner-min (one more job's setup and twenty ~27 MB downloads). On this window's mix that is roughly −900 of 1191 runner-min. The cost is wall clock: a ui job now starts after ui-deps, about one job-setup plus one upload (~1–1.5 min) later than it used to start its own build. On a warm run that is pure wall-clock loss; on a cold run it largely offsets the ~3–4 min build each job no longer runs. This PR's own CI run (all lanes, since ci.yml is in the ui filter) gives the first after-reading; the card gets the per-job before/after table once comparable PRs have run.

Option (b) of the card (a pull_request run reads the signed remote cache) is not taken. Turbo reads the remote with the same token that writes it, and Build Core's "Turbo remote cache (#21186)" comment withholds both secrets from PR code on purpose. Giving PRs a read would reverse that ruling, which is the maintainer's call and not a seat's.

Verification

Run Result
ui/ pnpm exec vitest run scripts/__tests__/turbo-cache-save-bound.test.ts at this head 44 passed
the same file against main's ci.yml (17c5e68b), restored with git checkout HEAD -- 38 failed, 6 passed (exit 1)
local seven-package build into a fresh cache dir, --summarize 36 tasks, 0 cached, 4m36s; cache dir 27 MB
the same build after deleting packages/spec/dist, from that cache dir 36 of 36 cached, FULL TURBO, 1.2 s, dist/ restored
turbo run build --dry=json with and without TURBO_RUN_SUMMARY / NODE_OPTIONS / TURBO_CACHE 36 identical task hashes

Derived gates (node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, 3 paths, at cd0ae004): 63 families. 62 exit 0. pnpm check:dual-build-cjs-loads exits 3, PREREQUISITE NOT MET: it reads the dist/ of every package, and this box has built only the ui closure. CI's Lint & Repo Gates runs it on a full build. --ran reconciles: "63 derived famil(ies) accounted for — 62 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)". The slowest, on a shared box: pnpm check:type-check-debt (410s), pnpm check:pm-dispatch-gates (346s), pnpm check:query-options-erasure (286s), node scripts/check-comment-mask-corpus.mjs (161s).

Push-environment battery (GITHUB_ACTIONS=true GITHUB_EVENT_NAME=push node scripts/pm/check-dispatch-gates.mjs, both tiers): it runs after this body was written, and its verdict line goes in the card report.

Acceptance notes

维护者速读(草稿)

改了什么: ui 的四个 CI 作业以前各自构建同样的 7 个 @objectstack 包。现在新增一个 Build UI Deps 作业,每次 CI 只构建一次,再把 turbo 缓存条目交给这四个作业,它们约 1 秒就能回放出构建结果。同时,所有 ui 缓存保存都加了时限,保存失败也不会让作业变红。Build & E2E UI 的超时按实测推导,从 45 分钟降到 25 分钟。

为什么改: 合并后的 51 次运行里,有 27 次是每个 ui 作业各自冷构建一遍(全量运行每次 33–77 runner-min),这一步合计 1191 runner-min。缓存保存没有时限,上传一旦卡住,已经通过的检查会被取消,合并队列会把它当成失败(objectui#6577 出过这个事故)。

风险与代价(含回滚): ui 作业要等 Build UI Deps 跑完才开始,墙钟大约多 1–1.5 分钟。冷构建时这部分基本被省下的构建时间抵消,热缓存时就是净增。下载失败时作业会自己冷构建,和现在一样,所以不会因此跳过或变红任何必需检查。回滚就是 revert 这个 PR。

席位意见:

你要做的: 这是 Tier H(.github/workflows/ci.yml),需要 os-zhuang 或 hotlong 批准。


Generated by Claude Code

…hand the ui jobs their turbo cache entries; bound every ui cache save

Claude-Session: https://claude.ai/code/session_01XpG2CKzNjLujzcMmSxKCHy
Co-Authored-By: Claude <noreply@anthropic.com>
…ff are pinned against the root ci.yml

Claude-Session: https://claude.ai/code/session_01XpG2CKzNjLujzcMmSxKCHy
Co-Authored-By: Claude <noreply@anthropic.com>
…oot jobs' prune and report (row F2)

Claude-Session: https://claude.ai/code/session_01XpG2CKzNjLujzcMmSxKCHy
Co-Authored-By: Claude <noreply@anthropic.com>
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 26, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review October 26, 2026 06:10
@os-zhuang
os-zhuang enabled auto-merge October 26, 2026 06:10
@os-zhuang
os-zhuang added this pull request to the merge queue Oct 26, 2026
Merged via the queue into main with commit 963e504 Oct 26, 2026
65 of 67 checks passed
@os-zhuang
os-zhuang deleted the claude/project-thread-kz44z5 branch October 26, 2026 07:04
os-tesla pushed a commit that referenced this pull request Oct 26, 2026
One lint-ui hunk: the Repo Gates' built-type block stays, followed by
#25963's prune step and bounded save, unchanged.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015MoCPtpv6AtTo899tF8Ktq
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cd size/l skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

3 participants