Skip to content

ci: fail the build when the docs Worker exceeds a declared size budget - #275

Merged
os-bill merged 1 commit into
mainfrom
claude/issue-262-worker-bundle-budget
Sep 8, 2026
Merged

os-bill merged 1 commit into
mainfrom
claude/issue-262-worker-bundle-budget

Conversation

@os-bill

@os-bill os-bill commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

Fixes #262

.github/workflows/ci.yml never measured the Worker bundle. The only thing that checked it was the Cloudflare API, at upload time, on main, after merge — and the rejection lands on version creation, so nothing 500s, no page changes, and the site just stops moving. That is how this repo ran 35 consecutive red deploys (runs #106-#140, 08-25 to 09-02) with the build job green for every one of them.

What changed

One file, three edits:

  1. Package the Worker from the build this job tested loses its push + refs/heads/main condition. It ran only where a deploy would follow, so a pull request never packaged a Worker and could never be told its Worker was too big — the whole defect. It now runs on every event.
  2. New step: Worker bundle fits the size budget. Reads wrangler's own Total Upload: line and exits 1 above a declared budget.
  3. Upload the Worker bundle keeps its main-only condition, and now sits behind the gate: an if: carrying no status function implies success(), so an over-budget Worker never becomes an artifact and never reaches deploy-docs.yml.

The measurement

wrangler deploy --dry-run prints Total Upload: from the same code path a real deploy uses — no API call, no credentials, 9s. That is deliberate over stat-ing files: stat-ing means re-deriving which files count, and that guess is the trap the card names. handler.mjs alone measures 48.48 MiB while the upload is 58553.98 KiB.

Verified from --dry-run --outdir, the uploaded set is exactly:

file bytes
worker.js 58383222
77d9…-resvg.wasm 1378357
8a4c…-Geist-Regular.ttf.bin 125956
a5d4…-yoga.wasm 71736
sum 59959271 B = 58553.98 KiB

which is the printed line to the hundredth. worker.js.map (85819146 B — larger than the whole budget) and the .open-next/assets and .open-next/cache trees are not in it.

Calibrated against a figure Cloudflare accepted. Same commit 0e26657f, run 33891143864, Worker version 2170b929-5879-4b3f-b7a2-9eda750158dd:

accepted upload   58555.94 KiB
this step         58553.98 KiB     1.96 KiB low = 0.0033 %

The budget

61440 KiB = 60 MiB = 93.75 % of the 65536 KiB limit. One constant, declared with the limit beside it; every percentage and headroom figure in the step output is computed from those two and never typed. The card's own banner is why that matters — it quoted 89.3 % (against 65536) and ~5.3 MiB headroom (against 64000) in one paragraph, two ceilings, about 1.5 MiB of phantom room.

It clears two bars:

bar figure verdict
today's bundle, measured on this branch 58553.98 KiB (89.35 %) under it — 2886.02 KiB of growth room, so not red on arrival
run #105, the last successful deploy before the outage 62747.87 KiB (95.75 %) over it by 1307.87 KiB — this gate would have gone red weeks early

No warn tier, deliberately. Every band between today's 89.35 % and the 93.75 % budget is under 4.5 points wide and the bundle is already inside it, so a warning would be lit from its first run and read as wallpaper. The reading is printed to the step summary on every run instead, which is what a warn tier was wanted for.

Demonstrated red

A probe that cannot fail is indistinguishable from one that passed, so the gate was ablated before it was trusted. Both legs ran the step body extracted from the parsed ci.yml, not a draft copy.

Padding the real bundle by 4 MiB — mutation confirmed on disk (marker present, 2278 to 4196623 bytes) rather than by an editor's exit code:

Total Upload: 62650.01 KiB          exit 1
| measured | 62650.01 | 95.60 % |
| budget   |    61440 | 93.75 % |
Headroom to budget: -1210.01 KiB

95.60 % lands within 98 KiB of run #105's pre-outage 62747.87 KiB, so this is close to a reconstruction of the state that preceded the outage. The measured size moved by 4096.03 KiB against a 4096 KiB pad — the gate tracks the artifact byte for byte, not a cached or hardcoded number.

Restoring the bundle (sha256 d05223bf…, byte-identical, marker count 0) returned it to exit 0 at 58553.98 KiB.

Negative control — wrangler output with the Total Upload: line absent:

### Worker bundle size — NOT MEASURED
::error::Worker bundle NOT measured — no 'Total Upload:' line in the wrangler dry-run output.
exit 1

An unreadable measurement is a finding, never a skip. A gate that silently weighs nothing passes forever.

Cost

Not a second build — --skipNextBuild re-packages the .next tree pnpm turbo run build already produced, which is the cheap option the card hoped for. Measured on this branch:

step wall
turbo run build (already paid) 101s
packaging, newly on PRs 24s
dry-run weigh-in 9s

A pull request pays about 33s more than before, not another 101s.

Scope

deploy-docs.yml, rollback-docs.yml and smoke-docs.mjs are untouched. The new-version-ID assertion already exists in deploy-docs.yml. The pre-merge rendering check on #274 is out of scope here and #274 remains open.

⚠️ Production-affecting: this edits ci.yml, and merging to main publishes the docs site.

🤖 Generated with Claude Code

https://claude.ai/code/session_01ChPQM8jamxLUfUAxwFpJ8S


Generated by Claude Code

Nothing in this repository weighed the Worker. The only thing that checked
it was the Cloudflare API, at upload time, on `main`, after merge — and the
rejection lands on version creation, so nothing 500s, no page changes, and
the site silently stops moving. That is how this repo ran 35 consecutive red
deploys (runs #106-#140) with the `build` job green for every one of them.

The `build` job already packaged the Worker with `--skipNextBuild`, but only
on a push to `main`, so a pull request never packaged one and could never be
told its Worker was too big. That condition is dropped; the artifact upload
stays `main`-only underneath the new gate.

The gate reads wrangler's own `Total Upload:` line via `wrangler deploy
--dry-run` — the same accounting a real deploy prints, no API call and no
credentials — rather than stat-ing files, which would re-derive which files
count. Budget 61440 KiB (60 MiB), declared once with the 65536 KiB limit
beside it; every percentage is computed, never typed.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Nothing measures the docs Worker bundle before deploy, so exceeding Cloudflare's 64 MiB limit fails silently after merge

2 participants