Skip to content

Bundle Size Gate: main's ceiling drift is only visible on the next contributor's PR #12392

Closed

Description

@bolichen97

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 Gate conclusion (not skipped), 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

  1. added
    bugSomething is not working
    area: ciGitHub Actions, AI review gates, readiness checks
    on Sep 20, 2026
  2. github-actions commented on Sep 20, 2026

    @github-actions
    Contributor

    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 all chunk 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 reads skipped on 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.

  3. added
    needs-investigationTriage: requires deep analysis before a fix
    and removed
    needs-triageAwaiting automated triage
    on Sep 21, 2026
  4. dwu96 commented on Sep 21, 2026

    @dwu96
    Contributor

    🤖 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 Gate DOES produce a conclusion on main pushes. Sampling the last four completed ci.yml main-push runs:

    run head Bundle Size Gate frontend files in the merge
    35542587997 56c4e3cfe skipped 0 of 1
    35541279639 02a77624b success frontend touched
    35540050071 b1075e779 skipped 0 of 5
    35537254973 e8298981b success website/electron/**

    The two skips are correct: 56c4e3cfe is a single backend test file and b1075e779 is docs + src/kiro_crew/**, so needs.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 at ci.yml:2836 and the push: branches: [main] trigger at ci.yml:5 are 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 cancelled main-push runs. Two of them were frontend-touching:

    Neither produced a Bundle Size Gate verdict, because the run never ran. ci.yml:20 sets cancel-in-progress to pull_request only, under the concurrency: block at ci.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 in ci.yml states 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 all chunk 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:

    1. Exempt main from eviction (its own concurrency group, or one per SHA) — gets a verdict per commit, costs runner capacity, and reverses a documented deliberate trade.
    2. Split the bundle measurement into a small separate workflow with its own concurrency group — cheap to run per commit, but Bundle Size Gate is a required check whose name is load-bearing for branch protection (ci.yml:2872-2873 warns about exactly this).
    3. Notify on the main runs that do complete red — smallest change, no CI-topology risk, but only catches drift that lands in a quiet window.

    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 Gate is success on 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-level only_backend condition at all, while the bundle gate's is at ci.yml:2836. That makes it a weaker precedent than the issue implies: the two gates differ in their if: 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.

  5. dwu96 commented on Sep 21, 2026

    @dwu96
    Contributor

    🤖 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 the all ceiling again in website/scripts/check-bundle-size.mjs:

    - all: 12240 * KB, // measured 11650.5 KB on fix/gatewayd-overload-liveness 2026-09-16 (5.1% headroom)
    + all: 12860 * KB, // measured 12244.5 KB on feat/browser-cookie-import 2026-09-20 (5.0% headroom)
    

    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:

    this branch ... builds the chunk at 12,538,405 B (12244.5 KB) against the 12240 KB ceiling -- 4.5 KB OVER. Attribution measured, not assumed: the branch adds 18 keys x 13 catalogs, 20,992 B of catalog bytes over its merge base, so main alone sits at about 12,517,413 B (12224 KB) -- 16 KB (0.13%) under the ceiling, the same drift as every note above.

    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 under website/; 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.

  6. self-assigned this
    on Sep 22, 2026
  7. CrysisDeu commented on Sep 22, 2026

    @CrysisDeu
    Contributor

    Investigation (autonomous, conductor-dispatched)

    Root cause: Confirmed as the earlier pass found: Bundle Size Gate is correctly wired to push: main but lives inside ci.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 the all chunk 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-size job: npm ci → vite build --mode analyze with --max-old-space-size=6144 → node scripts/check-bundle-size.mjs, gated if: 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-ceiling job = eslint ceiling only), :236-260 (Render ratchet verdict reads needs.*.result and manages the tracking issue). Ceiling raises continue: website/scripts/check-bundle-size.mjs:99 is now 12870 * KB // measured 12254.6 KB on main @ 34fe0de33e 2026-09-21 — the fifth raise since 2026-09-06. Current main cbdfa743e.

    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 job frontend-bundle-ceiling ("Frontend bundle-size ceiling") mirroring ci.yml bundle-size steps verbatim (checkout, setup-node 24 with npm cache, npm ci, analyze build, node scripts/check-bundle-size.mjs, chunk-cycle gate), unconditional (no only_backend guard — the audit judges the integrated tree), timeout-minutes: 25, large-memory runner selection copied from ci.yml:2830.
    • Add it to needs: of Render ratchet verdict and thread BUNDLE_RESULT: ${{ needs.frontend-bundle-ceiling.result }} into the tracking-issue script alongside RATCHET_RESULT/FRONTEND_RESULT, so an over-ceiling main shows up on main's own tracking issue before any PR inherits it.
    • Header comment: one line noting the bundle gate is mirrored here and why (the ceiling stays in check-bundle-size.mjs, one numeric source).
    • Acceptance is then demonstrable from a real run record on the first frontend-touching main push after merge; no ceiling value or the 5% convention changes.
      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".
  8. added
    auto-fixableTriage: mechanical fix, automated pipeline will handle
    and removed
    needs-investigationTriage: requires deep analysis before a fix
    claimedAn automated fix is in progress
    on Sep 22, 2026
  9. removed their assignment
    on Sep 22, 2026
  10. iamwhatever commented on Sep 23, 2026

    @iamwhatever
    Contributor

    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.

  11. pepmach commented on Sep 24, 2026

    @pepmach
    Contributor

    Datapoint, 2026-09-24 ~23:20Z, measured on pristine main 9270574684 with 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). With all: 13520 * KB (the current size plus the standard 5% headroom) the gate reads 713 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.

  12. bolichen97 commented on Sep 25, 2026

    @bolichen97
    ContributorAuthor

    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 gate on a merge ref of main:

    FAIL assets/all-Cl1q5JSY.js: 12.57 MB exceeds its 12.57 MB budget by 2.8 KB (chunk 'all')
    

    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:

    PR website/ files gate
    #13603 0 failure
    #13596 0 failure
    #13581 0 failure
    #13571 0 failure

    A 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 lazy import() boundary can move a catalog string out of all, 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.

  13. added a commit that references this issue on Sep 25, 2026
    c8909f5
  14. added a commit that references this issue on Sep 25, 2026
    a3d4c3d
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area: ciGitHub Actions, AI review gates, readiness checksauto-fixableTriage: mechanical fix, automated pipeline will handlebugSomething is not workingclaimedAn automated fix is in progress

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions