Repository navigation
Bundle Size Gate: main's ceiling drift is only visible on the next contributor's PR #12392
Description
Split out of #11388, at @pepmach's request on that thread ("Running the gate on push to main is worth doing too, but as its own change"). #11388's own instance — the all ceiling at 11620 KB while main measured 11625 KB — is closed; this is the recurrence-prevention half nobody has picked up.
The pattern
website/scripts/check-bundle-size.mjs carries its own record of the drift, and it is a cadence rather than three incidents:
| ceiling raised | measured | note in the script |
|---|---|---|
| 2026-09-06 | — | "main's own gate is red and every PR rebased onto it inherits the failure" |
2026-09-10 (3de52729c) |
11067 KB → 11620 * KB |
5% headroom |
2026-09-16 (#10521 63fc20e59b) |
11650.5 KB → 12240 * KB |
5.1% headroom |
Three raises in eleven days, each triggered the same way: ordinary i18n catalog-string growth pushes the eager all chunk past its ceiling, main goes over, and the red surfaces on the next contributor's PR. #11388 lists #11092 (11.0 KB over) and #8985 (9.1 KB over) as innocent PRs that carried it. A rerun does not help — the merge ref is stale, so each one needs a rebase push.
What is actually unsettled
Bundle Size Gate reads skipped on the latest completed main push run (35542587997). But it is also skipped on the PR run I sampled, so the skip comes from the job's if: needs.changes.outputs.only_backend != 'true' condition, not from PR-only scoping — ci.yml does trigger on push: branches: [main]. So the first question is not "add a push trigger" but:
When does Bundle Size Gate actually produce a conclusion on a main push, and does it ever? A frontend-touching merge to main should satisfy only_backend != 'true' and report. If it does, the gap is narrower than #11388 assumed and the fix may be a notification rather than a trigger. If it never reports on main, the trigger is the fix.
Settle that from real run records before changing the workflow.
Why this is worth doing rather than living with
The comparison case is already decided. #7597 recorded the identical "gate is effectively PR-only, so main drifts invisibly" structure for Coverage Gate, and that one has since closed the gap — Coverage Gate now reports on main pushes and is success on four of the last five:
35542587997 Coverage Gate=success
35541279639 Coverage Gate=success
35540050071 Coverage Gate=success
35537254973 Coverage Gate=success
So the bundle gate is the remaining member of a class whose other member is fixed, not an instance of a general policy that gates are PR-only.
Out of scope
Not "stop the catalog from growing" and not "raise the ceiling higher". Catalog growth is expected, and the ~5% headroom convention is working as intended — each raise has been a correct, documented re-measure. The only defect is who finds out: today it is the next contributor rather than the merge that caused it.
Acceptance
- A main push whose diff touches the frontend produces a
Bundle Size Gateconclusion (notskipped), demonstrated from a real run record. - When main is over a ceiling, that is visible on main's own run before a PR inherits it.
- No change to the ceiling values or to the ~5% re-measure convention.
Activity
github-actions commentedon Sep 20, 2026
Automated triage summary — generated from the issue text alone, before any maintainer has read it. Treat every line as a starting point, not a verdict.
The Bundle Size Gate effectively only fails on contributor PRs: when i18n growth pushes main's eager
allchunk past its ceiling, main's own CI doesn't surface it, so the next (innocent) PR inherits the red and needs a rebase push. Reporter notes the gate readsskippedon recent main pushes and asks whether it ever concludes there before any workflow change.Posted by
issue-summary.yml. The model had no access to the repository — it read only this issue and the titles of recent ones.dwu96 commentedon Sep 21, 2026
🤖 Kiro Crew Auto-Pipeline [operator: dwu96#8c08bee0]: Routing to
needs-investigation. You asked for the central question to be settled from real run records before anything changes, so I did that first — and the answer inverts the framing of both this issue and its closed parent.Bundle Size GateDOES produce a conclusion on main pushes. Sampling the last four completedci.ymlmain-push runs:Bundle Size Gate56c4e3cfeskipped02a77624bsuccessb1075e779skippede8298981bsuccesswebsite/electron/**The two skips are correct:
56c4e3cfeis a single backend test file andb1075e779is docs +src/kiro_crew/**, soneeds.changes.outputs.only_backend != 'true'is false and the gate has nothing to measure. Your sampled run (35542587997) happened to be the first of those. The condition atci.yml:2836and thepush: branches: [main]trigger atci.yml:5are both exactly as you describe — the gate is wired to main correctly and reports there today.So acceptance criterion 1 as written is already met, and the closed parent's parenthetical ("gate never runs on main") is false. That does not make this issue invalid — the drift you documented is real, three raises in eleven days. It means the mechanism is somewhere else.
The mechanism looks like concurrency eviction, not scoping. Between 20:54 and 20:59 on 2026-09-20 there are nine consecutive
cancelledmain-push runs. Two of them were frontend-touching:4fb639284"label the sidebar create button New chat ([App Store listing] Multiplex — cross-agent session overview #12299)" — 19 of 19 files underwebsite/7231d62f2"stop auto-opening the first card on arrival" — 3 of 5 underwebsite/Neither produced a
Bundle Size Gateverdict, because the run never ran.ci.yml:20setscancel-in-progresstopull_requestonly, under theconcurrency:block atci.yml:12, so main runs are not cancelled on purpose — but the concurrency group still permits just one pending run, and GitHub evicts that pending run when a newer one queues. The comment inci.ymlstates this outcome as an accepted trade ("main gets periodic verdicts instead of none", with a ~19min run against a ~1.4min median merge gap).That is a much better fit for the pattern you documented than a scoping gap: a burst of frontend merges is exactly when the
allchunk grows, and it is exactly when main's own runs get evicted. A single frontend merge in a quiet window measures fine; the merges that actually move the ceiling arrive in bursts and go unmeasured.Why this needs a human rather than an automated fix. The remedies now point in three different directions and they are not equivalent:
Bundle Size Gateis a required check whose name is load-bearing for branch protection (ci.yml:2872-2873warns about exactly this).Picking among those is a CI capacity-and-policy judgment, and (2) carries a branch-protection hazard the file itself flags. Worth noting (3) is mechanically derivable on its own if a maintainer wants the narrow version, and it composes with either of the others.
One correction to the comparison case.
Coverage Gateissuccesson all four sampled main runs, including the two where the bundle gate correctly skipped — so it is not skip-gated on the frontend surface flag the way the bundle gate is —Coverage Gate(ci.yml:1992) carries no job-levelonly_backendcondition at all, while the bundle gate's is atci.yml:2836. That makes it a weaker precedent than the issue implies: the two gates differ in theirif:condition, not only in whether they reached main. The #7597 parallel still holds for the notification half.Nothing here changes a ceiling value or the ~5% re-measure convention.
dwu96 commentedon Sep 21, 2026
🤖 Kiro Crew Auto-Pipeline [operator: dwu96#8c08bee0]: Follow-up evidence found while scanning PRs opened in the last few hours — the cadence you documented has a fourth instance, and it is in flight right now.
Open PR #12377 (
feat(browser): import cookies into the agent's browser from the dashboard) raises theallceiling again inwebsite/scripts/check-bundle-size.mjs:That is raise number four in fifteen days, four days after the third — it extends your table rather than sitting outside it, and it was filed the same day as this issue by a different contributor.
The note that PR adds is the strongest single piece of evidence for why this issue matters, because it did the attribution arithmetic:
So a contributor whose feature adds 18 i18n keys was 4.5 KB over, of which their own change accounts for 20 KB — meaning main had already consumed all but 16 KB of the previous 5% headroom before they arrived. They absorbed the re-measure for drift that was not theirs, which is exactly the cost this issue is about. And 0.13% of headroom means the next frontend merge of any size takes main over on its own.
This also sharpens the eviction mechanism from my routing comment above. With main sitting 16 KB under a ceiling, whether the overrun is caught on main depends entirely on whether main's own run survives long enough to measure — and in the 20:54-20:59 window on 2026-09-20 nine consecutive main runs were evicted, two of them frontend-touching (
4fb639284, 19 of 19 files underwebsite/;7231d62f2, 3 of 5). A gate that is correctly wired to main and 0.13% from red is still silent if its run never executes.No action taken on either issue or PR — recording this so whoever picks up the notification-vs-topology decision has the live instance in front of them, and so the fourth raise is on the record here rather than only in a diff comment.
CrysisDeu commentedon Sep 22, 2026
Investigation (autonomous, conductor-dispatched)
Root cause: Confirmed as the earlier pass found:
Bundle Size Gateis correctly wired topush: mainbut lives insideci.yml, whose ref-keyed concurrency group lets a newer main push evict the pending run, so on a burst of frontend merges the commits that move theallchunk get no measurement and the red surfaces on the next PR. What was missed: the repo already settled how this class is handled..github/workflows/main-ratchet-audit.yml(issue #7511) exists precisely to run the cheap ceiling/ratchet gates on every main push, per-SHA concurrency, never cancelled, and to open/update/close one tracking issue — it just does not carry the bundle-size gate.Evidence:
.github/workflows/ci.yml:12-20(ref-keyed group; comment states the eviction trade),:2826-2870(bundle-sizejob:npm ci→vite build --mode analyzewith--max-old-space-size=6144→node scripts/check-bundle-size.mjs, gatedif: needs.changes.outputs.only_backend != 'true'),:2872-2873(required-check name must stay)..github/workflows/main-ratchet-audit.yml:1-35(rationale: "drift on main surfaces later on an UNRELATED pull request … the cheapest way out is to raise the ceiling"),:71-78(group: main-ratchet-audit-${{ github.sha }},cancel-in-progress: false),:193(frontend-ceilingjob = eslint ceiling only),:236-260(Render ratchet verdictreadsneeds.*.resultand manages the tracking issue). Ceiling raises continue:website/scripts/check-bundle-size.mjs:99is now12870 * KB // measured 12254.6 KB on main @ 34fe0de33e 2026-09-21— the fifth raise since 2026-09-06. Current maincbdfa743e.Verdict: auto-fixable
Proposed fix scope / decision needed: Extend the existing audit rather than touch
ci.yml(no concurrency or required-check change, so none of the three hazards the earlier pass listed apply). ~60-80 workflow lines:main-ratchet-audit.yml: add jobfrontend-bundle-ceiling("Frontend bundle-size ceiling") mirroringci.ymlbundle-sizesteps verbatim (checkout, setup-node 24 with npm cache,npm ci, analyze build,node scripts/check-bundle-size.mjs, chunk-cycle gate), unconditional (noonly_backendguard — the audit judges the integrated tree),timeout-minutes: 25, large-memory runner selection copied fromci.yml:2830.needs:ofRender ratchet verdictand threadBUNDLE_RESULT: ${{ needs.frontend-bundle-ceiling.result }}into the tracking-issue script alongsideRATCHET_RESULT/FRONTEND_RESULT, so an over-ceiling main shows up on main's own tracking issue before any PR inherits it.check-bundle-size.mjs, one numeric source).Cost to state in the PR: one analyze build per main push (same build every PR revision already pays), which is the only thing a maintainer might weigh; the audit's own header already accepted "added runner minutes … only the ratchet lanes".
iamwhatever commentedon Sep 23, 2026
Triage note (verified against main 151bfcb): the title premise is wrong in mechanism, and the real defect is worse. The Bundle Size Gate IS wired to main pushes and does conclude there. Of the last 25 completed ci.yml main-push runs: 4 real bundle measurements, 6 correctly skipped (backend-only), and 15 where the job was never created because the run was evicted by the concurrency group (five consecutive main SHAs queued within 40 s, all cancelled). The evictions cluster on exactly the burst merges that grow the all chunk, so main almost never gets measured when it matters. A fifth ceiling raise landed the day after this issue was filed (4a09450, #12472), and its own note records another innocent PR with nothing under website/ eating the re-measure. Cheapest mechanical route is a frontend-bundle-ceiling job in main-ratchet-audit.yml (per-SHA concurrency, cancel-in-progress false), but that workflow's header reserves the runner-cost ruling for a maintainer, so this stays a decision, not an auto-fix.
pepmach commentedon Sep 24, 2026
Datapoint, 2026-09-24 ~23:20Z, measured on pristine main
9270574684with CI's exact command:FAIL assets/all-Cl1q5JSY.js: 12.57 MB exceeds its 12.57 MB budget by 2.8 KB (chunk 'all')-- 13,181,750 B (12872.8 KB) over the 12870 KB budget, 14 modules in the chunk (13 catalogs + the entry). Main shows no verdict for it only because every push run since ~15:37Z was cancelled or starved (#12800 / #13564); the job fails on the first completed frontend-touching run. Four PRs today failed the gate on their merge refs for growth that is main's (+1.7 KB to +18.1 KB of their own). Withall: 13520 * KB(the current size plus the standard 5% headroom) the gate reads713 chunks within budget (default 500.0 KB, 14 allowlisted). #13579 is the heal in flight; PRs are citing this issue and #13579 rather than carrying their own bump.bolichen97 commentedon Sep 25, 2026
Recurrence, fourth time by this file's own count, and it is blocking every PR opened on today's main.
Measurement
Bundle Size Gate/Per-chunk size gateon a merge ref of main:The ceiling is
all: 12870 * KB, set from a measurement of 12254.6 KB with 5.0% headroom. Main has grown 618 KB since, so the whole 5% is spent and the chunk is 2.8 KB over.Attribution, measured rather than assumed
Four open pull requests whose diffs contain ZERO files under
website/are failing this gate right now:website/filesA gate that measures built frontend chunks cannot be failed by a diff with no frontend files, so each of those builds main's tree verbatim. The pull requests that still pass (#13579, #13565, #13561) all have older bases, which is exactly the shape this issue describes: the drift is invisible on main and surfaces on whoever opens next.
Why it is not attributable to one merge
Same as the three notes already in
CHUNK_BUDGETS: the chunk holds the 13 shipped i18n catalogs plus the entry, and no lazyimport()boundary can move a catalog string out ofall, so every catalog addition lands here. The growth is accumulation across merges, not one change.What clears it
The file's own convention: re-measure main's tip and set the ceiling to that measurement plus roughly 5% headroom, with a note recording the measurement. Against the current 12872.8 KB that is about 13516 KB.
Raising a gate threshold is a maintainer decision here, so this comment is the proposal and not the change. Noting it on this issue rather than in an affected PR because both of mine are CI-only and a threshold bump does not belong in either.
fix(ci): measure the bundle ceiling on main, not just on the next PR
fix(ci): measure the bundle ceiling on main, not just on the next PR (k…