Skip to content

ci(ui-merge): twenty ui jobs each rebuild the same seven @objectstack packages (1.6–2.5 min apiece, mostly turbo cache misses on PRs) — build them once per run and hand the dists to the ui lanes (parent #25677) #25869

Closed

Description

@objectstack-fleet

Filing gate ③ (maintainer-directed) + ④: aftercare of the ui/ merge (#25752), part of #25677. Touches .github/workflows/ci.yml = Tier H: GOVERNED_APPROVERS review, the PR stops at draft.

Maintainer, 2026-10-26 01:45Z, in the project thread that measured post-merge CI speed: 「创建issue」 (asked after reading the ranked fix list; this is item 3).

谁付代价(业务筛): the fleet's runner budget, and the wall clock of ui PRs. Every run that schedules the ui lanes repeats one build 20 times (Test UI ×16, Type Check UI, Lint UI, Build & E2E UI): turbo run build --filter=@objectstack/spec... --filter=@objectstack/client... … for the seven packages objectui consumes. When cardholders are running in parallel, this is ~40 runner-min per run, plus 1.6–2.5 min on the critical path of a ui-only PR.

Measured (run 40248884869, 2026-10-26)

Wanted

The seat measures two options and picks the cheaper one per run:

  • (a) one ui-deps job builds the seven packages once and uploads their dist/ as an artifact, and the ui lanes download it;
  • (b) pull_request runs read the signed turbo remote cache, still with no write.

⛔ Do not change what the ui lanes test, or their if:.

Acceptance

  • On the card: total ui-lane runner-min and Test UI shard wall time, before and after, on comparable PRs.
  • The gate rosters follow; each --self-test exits 0.

Expected saving: ~40 runner-min per ui-scheduling run, 1–2 min per ui lane. Size: S–M. Clause-②: no. Metadata-protocol: none.

Dedupe

search_issues 2026-10-26 01:50Z 「ui jobs build objectstack packages once artifact turbo remote cache pull_request」: 20 hits. The relevant ones are closed: #21186 (wire the remote cache) and #22077 (extend it to Lint / Test Core). Neither covers the ui lanes added at cutover. New.

Measurement report: /mnt/project-files/d1-merge/out/ci-speed/post-merge-pr-ci-speed.md.

Activity

  1. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Epic PM: two rows added from the 2d-1 census 2026-10-26T02:24Z · session_01RSDL3SFtJaKzcymvCD8RsU

    Not a claim; this card stays held until #25867 and #25868 land. Two follow-up rows from #25879 (4b 2d-1, the ui/scripts/__tests__ readers of the nested ui/.github/workflows/ci.yml) belong to this card because they are the same surface — the root * UI jobs' Turbo cache steps — and the seat that measures options (a) and (b) touches exactly those steps:

    Acceptance addition: the seat's before/after table also shows the save step's duration and that a save that exceeds its bound cannot red the job. Part of #25677.

  2. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: epic #25677 round 8 (post-cutover CI speed card 3; Tesla 2026-10-26 01:45Z 「创建issue」; held until #25881 and #25882 landed, both merged 03:27Z) 2026-10-26T05:20Z
    Session: session_01RSDL3SFtJaKzcymvCD8RsU
    Account: os-tesla
    Branch: claude/project-thread-kz44z5 (the card thread's designated branch in objectstack-ai/objectstack — the thread can push only this branch, a measured platform exception to claude/issue-<n>-<slug>)
    Worktree: objectstack-issue-25869
    Domain: domain:devx
    Seat: epic:#25677
    Scope: the whole card — build the seven @objectstack/* packages the ui lanes consume once per run and hand their dists to the ui jobs instead of each job rebuilding them; plus row F1 of comment 6406837199 (bound the root ui jobs' cache saves the objectui way, F11's combined pnpm-store cache included), row F2 of the same comment, and F13 from #25940's body (derive and pin the root lint-ui 30 / build-e2e-ui 45 timeout-minutes); push/schedule keep the full battery (Q6-b); the gate rosters follow with --self-test 0; Tier H (draft, os-zhuang + hotlong), skip-changeset
    Why now: twenty ui jobs each rebuild the same seven packages (1.6–2.5 min apiece, mostly turbo cache misses on PRs); #25881 and #25882 cut the queue and PR shard counts, so this rebuild is now the largest fixed cost left per ui run
    Container & model: M, mode:thread (thread session session_01XpG2CKzNjLujzcMmSxKCHy; order: this card's body, FRAME.md applies), model: opus, effort: xhigh
    Clause-②: no
    Metadata-protocol: none
    Responsibility: n/a — epic step
    Thread-read: none
    Serial constraints cleared: #25902 (#25880, open, Tier H) adds gate steps inside lint-ui of the same ci.yml — ⛔ do not touch those steps; rebase over it when it lands. 4b 2d-3c (#25712, not yet opened) deletes ui/.github/workflows/** and adds one lint-ui step; whichever lands second rebases. ⛔ Do not change the four ui jobs' if: lines, the filter outputs or the Test UI shard selection; a failed dist hand-off must fall back to building in the job, never to skipping it. Report the push-environment battery (GITHUB_ACTIONS=true GITHUB_EVENT_NAME=push node scripts/pm/check-dispatch-gates.mjs, 2182/0) before the PM flips ready

  3. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    #25869 after-readings: the seven packages built once per run (#25963) · 2026-10-26T08:33Z · session_01XpG2CKzNjLujzcMmSxKCHy

    #25963 landed through the queue on os-zhuang's approval. The readings below come from the Actions jobs endpoint (completed_at minus started_at, per job and per step). Before = the 51 CI runs that scheduled ui jobs between the ui/ cutover (#25825) and the landing window opening. After = the 19 completed CI runs since #25963 landed that ran Build UI Deps. That includes push runs 40283575048, 40284424233, 40289373917 and 40289957670 (the #25902 merge), plus queue entries 40286327711, 40287733434 and 40290196603. Two cancelled PR runs are left out.

    The seven-package build (the card's question)

    before after
    warm run: the build step per ui job 0–2 s from each job's own seed (24 runs) Build UI Deps builds in 1–4 s; every ui job replays in 0–2 s (13 runs)
    cold run: who builds every ui job, 86–288 s each (median 201 s; 27 runs) Build UI Deps once, 131–252 s; every ui job replays in 0–2 s (6 runs: 2 queue entries with 19 ui jobs, 4 PRs with 2)
    runner-min on that step, cold full-lane run 33–77 per run (e.g. queue entry 40271867662: 70.2) queue entry 40286327711: 3.8; queue entry 40290196603: 3.1
    runner-min on that step, cold two-lane run 7–8.5 3.4–4.4
    runner-min on that step, all runs 1191 over 51 runs (23.4 per run) 26.4 over 19 runs (1.4 per run)
    hand-off cost none the 27 MB download: n=293, median 2 s, max 16 s

    Wall clock (Premise vs measured, point 1)

    The ui jobs now start when Build UI Deps finishes:

    • Warm runs: the ui jobs start 42–73 s after Build UI Deps starts, because that job's own total is 39–71 s (13 runs). This is pure wall-clock cost, about +1 min per ui lane.
    • Cold runs: the ui jobs start 224–290 s after Build UI Deps starts (6 runs). Before, each job spent 86–288 s (median 201 s) building inside its own wall. The net change is roughly one job setup, about +0.5–1 min.

    The card's expected "1–2 min per ui lane" wall saving does not hold. The change saves runner-minutes and adds about a minute of wall clock per lane.

    Per-job wall clock, whole job (n, median, max)

    These populations are different PR mixes, so job medians compare loosely. The step readings above are the like-for-like comparison.

    job before after
    Build UI Deps (did not exist) n=19, median 65 s, max 288 s
    Type Check UI n=49, median 224 s, max 1194 s n=18, median 607 s, max 889 s
    Lint UI n=42, median 112 s, max 558 s n=14, median 337 s, max 685 s
    Build & E2E UI n=51, median 170 s, max 457 s n=19, median 137 s, max 166 s
    Test UI shard n=591, median 650 s, max 1060 s n=223, median 485 s, max 784 s

    Lint UI with #25902's gates in the job (runs since its merge): n=4, at 259 s, 429 s, 433 s and 685 s (push 40289957670). The 685 s run exceeds the population the 30-minute ceiling was derived from (max 558 s). By objectui's rule (≥ 3× max and ≥ max + 15 min), 685 s gives 35. The 30 still leaves 2.6× headroom over the slowest run seen, but n=4 is too few to re-derive from. That re-derivation belongs to the #25880 seat (ui/scripts/__tests__/turbo-cache-save-bound.test.ts pins Lint UI at 30).

    Saves (rows F1/F11)

    Every ui cache save is now a bounded, non-fatal actions/cache/save (5 min). The turbo seed saves after the landing took 0–2 s (n=12). Build UI Deps did not save the pnpm store in this window, because every run hit its exact key. No save step ran into its bound.

    Premise vs measured, point 2

    Option (b), a pull_request run reading the remote cache, was not taken. Turbo reads with the same TURBO_TOKEN that writes, and Build Core's credential ruling (#21186) withholds it and the signing key from PR code. The PM ruled that the ruling stands. Queue and main runs of Build UI Deps carry the remote-cache lines.

    Local evidence at the PR head (cd0ae004)

    • Push battery, both tiers: "✓ dispatch-gates self-test: 2182 cases pass." (fast 1917 in 341.8 s, slow 265 in 872.1 s).
    • Derived gates: 63 of 63 exit 0. check:dual-build-cjs-loads was first a PREREQUISITE NOT MET and passed after a full pnpm build. --ran reports 0 unrun.
    • The PR's only CI red was Test UI 14/16, on main's known pre-install-import-graph row.

    Relay writes for this card: 2 (pr_create, this comment). MCP calls: 0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions