Skip to content

fix(bundle-budget): emit the stats fields the nightly gate reads - #1283

Merged
sudomaggie merged 1 commit into
developfrom
fix/nightly-bundle-budget-stats
Sep 5, 2026
Merged

fix(bundle-budget): emit the stats fields the nightly gate reads#1283
sudomaggie merged 1 commit into
developfrom
fix/nightly-bundle-budget-stats

Conversation

@Harry19081

Copy link
Copy Markdown
Member

Problem

The Frontend (production bundle budget) job added to Nightly full checks in #1249 failed on its first scheduled run (run 33930333000):

check-bundle-budget: stats has no `main` entrypoint.
ELIFECYCLE  Command failed with exit code 2.

The production build itself succeeded. pnpm build:stats runs webpack --json build/stats.json, and webpack-cli serializes that dump with the config's own stats object (webpack-cli/lib/webpack-cli.js: stats.toJson(compiler.options.stats)). config/webpack.config.js sets stats.all: false to keep console output terse, and all overrides its sibling preset, so the JSON carried only {time, errors, warnings}. scripts/quality/check-bundle-budget.mjs exited 2 on the missing entrypoint; stats.assets, chunk.modules and chunk.origins were absent for the same reason. The gate has never produced a measurement, and pnpm analyze was being handed the same near-empty dump.

Solution

Split the stats object on argv.json, which only build:stats and analyze set. JSON dumps get exactly the fields the budget script reads — entrypoints, assets, chunks, chunkModules, chunkOrigins, nestedModules — plus dependentModules and cachedModules. Those last two matter more than they look: with all: false webpack collapses dependent modules into "N dependent modules" placeholders and drops every module the filesystem cache served, which both under-counts the boot graph (2,610 real src modules were reported as 27) and makes the number depend on whether the cache was warm.

Everything else stays off, so module source is still excluded and build/stats.json lands at ~60 MB rather than gigabytes. Ordinary builds keep the identical terse console block — every other caller (build, build:release, build:mobile-native, scripts/dev/webpack-server.js, the light-config tests) passes an argv without json.

Resulting invariant: a --json build emits an entrypoint, per-chunk modules and origins, and an untruncated module list independent of cache state. Both branches are asserted in scripts/dev/webpack-config-light.test.cjs.

Potential risks

  • The nightly job will still be red after this lands — now on a real measurement, which is the gate working. On develop (2daa781) the boot bundle is 6.52 MB JS vs the 6.39 MB budget (+2.0%); maxSrcModules passes at 2,610 / 2,850. At 947baff, the merge that set the budget, the same script measures 5.93 MB / 37 chunks / 2,585 modules — matching the 37 chunks recorded in the script's header, so the budget was sound when written and the boot graph grew ~604 KB in a day. See Follow-up; deciding whether to shrink the graph or ratchet the number is a separate change and does not belong in this PR.
  • build/stats.json grows from ~19 KB to ~60 MB in that job's workspace. It is transient and not uploaded as an artifact.
  • pnpm analyze now receives these fields too. It was previously getting the empty dump, so this can only improve it, but the analyzer output is unverified here.
  • Measurements below are macOS. Chunk ids and byte counts will differ slightly on the ubuntu runner; the stats-option resolution being fixed is platform-independent.
  • pnpm scripts:test is not wired into CI or any hook, so the new assertions run only when someone runs that suite by hand.
  • Committed from a git worktree with hooks bypassed (core.hooksPath=/dev/null; the worktree has no .husky/_/husky.sh), so the Pre-commit hook ran. trailer is absent and lint-staged did not run. The equivalent checks were run by hand — see Verification.

Verification

Run in a clean git worktree of origin/develop with node_modules symlinked from the main checkout.

  • Root cause reproduced in isolation, on a two-file webpack project using this repo's exact stats block: --json output keys were time, errors, warnings; entrypoints, assets and chunks all missing. Adding the fields in this diff restored every one.
  • NODE_OPTIONS=--max-old-space-size=6144 npx webpack --config config/webpack.config.js --mode production --json build/stats.json at 2daa781 → exit 0, build/stats.json 60,850,713 bytes.
  • node scripts/quality/check-bundle-budget.mjs → runs to completion, exit 1: Boot bundle (39 chunks) / JS 6.52 MB budget 6.39 MB / src JS mods 2610 budget 2850, area breakdown led by engines/ChatPanel 421, engines/SessionCore 185, features/Org2Cloud 161 — the shape the script's header describes. Before this change the same command exited 2 without measuring anything.
  • Same build + gate at 947baff (budget-setting commit, this diff applied on top): Boot bundle (37 chunks) / JS 5.93 MB / src JS mods 2585 → passes. This is what establishes that the harness is fixed and the remaining failure is real growth.
  • node --test scripts/dev/webpack-config-light.test.cjs → 5 pass, 0 fail (3 existing + 2 new).
  • node --test scripts/dev/*.test.cjs → 45 pass, 2 fail. Both failures are in scripts/dev/startup-watchdog.test.cjs, which asserts on src-tauri/src/lib.rs text; they fail identically on unmodified 947baff, so they are pre-existing and unrelated.
  • npx prettier --check on both changed files → clean. ESLint is not applicable: package.json#eslintConfig ignorePatterns (/*, !/src) ignores everything outside src/, and scripts/ci/select-lint-targets.cjs filters CI's changed-file lint to src/-prefixed paths, so neither file reaches ESLint.
  • Not run: full vitest, tsc/tsgo, and Rust checks. No src/ or Rust file is touched; CI covers all three on this PR.

Follow-up

Not part of this change, but the diff that explains the 2.0% overage, comparing boot chunks at 947baff2daa781: vendors +576 KB, plus two newly shared chunks (common, and one carrying ./src/mobileRemoteEntry.tsx origins) worth +634 KB, against main −193 KB. Only 25 src modules became newly reachable (2,585 → 2,610), so this is almost entirely re-splitting, not new application code: #1271 added a second mobile entrypoint to entry, and splitChunks now hoists code shared by the two entries into chunks the desktop shell also loads. Worth deciding whether the mobile entry should share the desktop vendors chunk before ratcheting config/bundle-budget.json.

🤖 Generated with Claude Code

`pnpm build:stats` runs `webpack --json`, and webpack-cli serializes that
dump with `compiler.options.stats`. That object sets `all: false`, which
overrides its own `preset`, so the JSON carried only `{time, errors,
warnings}` and check-bundle-budget.mjs exited 2 with "stats has no `main`
entrypoint" — the nightly job failed on its first scheduled run.

Widen stats for `--json` dumps only, to the fields the budget script
reads: entrypoints, assets, chunks with their modules and origins, plus
nested, dependent and cached modules, which `all: false` would otherwise
collapse into placeholders or drop on a warm filesystem cache. Console
output for ordinary builds is unchanged. Module `source` stays excluded.
@sudomaggie
sudomaggie merged commit dc4bc5c into develop Sep 5, 2026
6 checks passed
@Harry19081 Harry19081 added maintenance Maintenance, CI, build, release, cleanup, or tooling work dev-tooling Developer tooling, build, CI, tests, diagnostics, or release labels Sep 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dev-tooling Developer tooling, build, CI, tests, diagnostics, or release maintenance Maintenance, CI, build, release, cleanup, or tooling work

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants