Skip to content

feat(build): a control for bun, which builds this byte-identically and 2x faster - #307

Merged
oddharsh merged 3 commits into
mainfrom
exp/bun-canary
Aug 10, 2026
Merged

feat(build): a control for bun, which builds this byte-identically and 2x faster#307
oddharsh merged 3 commits into
mainfrom
exp/bun-canary

Conversation

@oddharsh

Copy link
Copy Markdown
Owner

Curiosity about bun 1.4 deserved a measurement rather than a guess, so this adds one: npm run bun:check, in the same idiom as kitesurf:check. Three questions, run when a bun release lands, never in CI.

bun:   1.4.0-canary.1+827475e21
node:  v26.7.0

  ok   zstd honours `dictionary` — 73 none / 24 good / 73 wrong
  ok   build output is byte-identical — 1975 files, node 14.4s vs bun 7.1s
  ok   contract suite passes under bun — 206 pass, 0 fail

Byte-identical is the bar

It is much higher than "the build succeeds". /a/ and /i/ are content-addressed, so a single differing byte mints a new URL, orphans every committed a-dict snapshot naming the old hash, and moves the CSP hashes the documents are served under. A build that is 2x faster and one byte different is not a faster build. This walks both trees and compares sha256 per file: 1975 files, 0 differing.

Viable, and still not adopted

Three reasons, and only the first is about bun:

  1. The newest stable bun is 1.3.14 (2026-05-13), which predates the dictionary fix (oven-sh/bun#34427, merged 2026-07-18) and silently ignores zstdCompressSync's dictionary. Pinning the build path to a canary trades a correctness bug for an unreleased revision.
  2. wrangler, miniflare and workerd are the deploy path and the route oracle, and they are node-pinned.
  3. The win is seconds on a step CI already spends far longer on in dry-runs.

The failure would be loud

Worth stating, because it reads scarier than it is: build.mjs already feature-detects the same collapse and throws (search expected a collapse), so bun 1.3.14 kills the build rather than shipping no-op deltas. That guard exists because the no-op shipped for a full deploy once. The engine is silent; this build is not.

Bun found a real defect in our suite

The byproduct worth keeping even if bun is never adopted. withSecurityHeaders rebuilds every response as new Response(response.body, …), which per Fetch locks the body it was handed, and one contract test pushed the same four case objects through it twice:

TypeError: Body object should not be disturbed or locked
  at withSecurityHeaders (holding/_worker.js/lib/security.js:232:10)
  at contract-tests.mjs:3384        <-- the second pass

Bun is spec-correct; node's undici is lenient. The assertions there are about headers, so the leniency was never load-bearing, it just made the suite depend on which runtime ran it. Cases are built fresh per pass now and it passes on both.

Verified in both directions

  • canary: all three green, tree clean afterwards
  • 1.3.14: fails at question 1 and stops before spending two builds, tree clean afterwards

That second run also caught two bugs in the check itself: process.exit() skips finally, which had left a half-written .build/ beside an orphan baseline, and running the comparison after a disqualifying probe just reproduces build.mjs's own throw 40 seconds later.

Gotcha 28 records the numbers, the bar, and the re-check recipe.

🤖 Generated with Claude Code

…d 2x faster

Bun 1.4 is worth a real answer rather than a guess, so `npm run bun:check`
measures one instead. Same idiom as `kitesurf:check`: three questions, run when
a bun release lands, not in CI.

Measured 2026-08-10, node v26.7.0 against bun 1.4.0-canary.1+827475e21:

  ok  zstd honours `dictionary` — 73 none / 24 good / 73 wrong
  ok  build output is byte-identical — 1975 files, node 14.4s vs bun 7.1s
  ok  contract suite passes under bun — 206 pass, 0 fail

BYTE-IDENTICAL is the bar, and it is a lot higher than "the build succeeds".
/a/ and /i/ are content-addressed, so one differing byte mints a new URL,
orphans every a-dict snapshot naming the old hash, and moves the CSP hashes the
documents are served under.

Not adopting it, for three reasons and only the first is about bun. The newest
STABLE bun is 1.3.14, which predates the dictionary fix (oven-sh/bun#34427) and
silently ignores `zstdCompressSync`'s `dictionary`. wrangler, miniflare and
workerd are the deploy path and the route oracle, node-pinned. And the win is
seconds on a step CI already spends longer on in dry-runs.

The failure would be LOUD, which the check now says out loud: build.mjs already
feature-detects the same collapse and throws, so 1.3.14 kills the build rather
than shipping no-op deltas. The engine is silent; this build is not. Verified
in both directions — canary all green, 1.3.14 fails at question 1 before
spending two builds, and the tree comes back clean either way.

Also fixes a real defect bun found in our own suite. `withSecurityHeaders`
rebuilds every response as `new Response(response.body, …)`, which per Fetch
LOCKS the body, and one contract test pushed the same four case objects through
it twice. Bun throws `Body object should not be disturbed or locked`; node's
undici allows it. The assertions are about headers, so the leniency was never
load-bearing — it just made the suite depend on which runtime ran it. Cases are
built fresh per pass now, and the test passes on both.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 10, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
aadhar-sh 2015f8b Aug 10 2026, 04:13 PM

@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Wire-size diff vs merge base

0a62f5cc6a8553

Brotli q11, the quality the edge serves. Every number here is deterministic:
an unchanged file produces no row. Advisory — this check fails on nothing.

Client assets

No change. 12 files, 60.92 KiB Brotli total.

Pages

No change. 46 files, 471.25 KiB Brotli total.

Worker bundle

No change. 268.62 KiB gzip.

Dictionary deltas (pd/)

No change. 134 deltas, 388.99 KiB total.

oddharsh and others added 2 commits August 10, 2026 12:08
Two more instances on 2026-08-10 (#305, #307), so four across four unrelated
PRs, every one alongside a CodeQL pass.

#305 is what makes this worth writing down rather than just recounting. It
changed documentation, four code COMMENTS and one quiz string, and failed the
check identically to a build-script PR, with `title: null` and a lone
annotation at `.github:211`. A diff with no executable change cannot carry a
security finding, so a check that reddens on it is reporting on itself. That is
a sharper discriminator than the API tell when the API tell is ambiguous.

Also records where to confirm the stakes: the ruleset, not the check list.
`validate` is the only required context, so this has been red on four PRs while
gating none of them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@oddharsh
oddharsh enabled auto-merge (squash) August 10, 2026 16:12
@oddharsh
oddharsh merged commit e44ec7e into main Aug 10, 2026
6 of 7 checks passed
@oddharsh
oddharsh deleted the exp/bun-canary branch August 10, 2026 16:13
oddharsh added a commit that referenced this pull request Aug 10, 2026
check-bun.mjs landed on main while this branch was in flight. Its usage strings
say `npm run bun:check -- --bun <path>`, and under pnpm the `--` is passed
THROUGH to the script rather than stripped, so the documented invocation would
hand check-bun.mjs a literal `--` as its first argument.
oddharsh added a commit that referenced this pull request Aug 10, 2026
#313 was CSS-only and failed `github-advanced-security` with the artifact's
whole signature: title null, empty summary, one annotation, CodeQL green.

Two things it added beyond the tally. The annotation sat at `.github:213`, the
SAME line as #307 on a diff sharing no content with it, so the line number is a
fingerprint of the harness rather than a location — do not go looking for what
lives there.

And `gh pr checks` did not list the failing check AT ALL: seven rows, every one
pass or skipping, while `/commits/<sha>/check-runs` reported that same check as
failure on the same head commit. Reproduced twice minutes apart, so it is not a
timing gap. That matters because the convenient command reports the PR green
while the notification says a check failed, which reads exactly like a stale
alert and is not one. The `check-runs` call the note already recommends is the
source of truth for this check.

Co-authored-by: Aadharsh Pannirselvam <19518661+oddharsh@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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.

1 participant