Skip to content

docs(compression): node 26 gave brotli a dictionary, so retire that limit - #305

Merged
oddharsh merged 2 commits into
mainfrom
docs/brotli-dictionary-node-26
Aug 10, 2026
Merged

docs(compression): node 26 gave brotli a dictionary, so retire that limit#305
oddharsh merged 2 commits into
mainfrom
docs/brotli-dictionary-node-26

Conversation

@oddharsh

Copy link
Copy Markdown
Owner

Gotcha 14 said node:zlib's brotli has NO dictionary parameter, listed the nine params as the whole list, and rested part of the dcz-over-dcb case on dcb needing the brotli CLI back in the build path. That expired.

Node 26 takes a dictionary option (nodejs/node#61763, merged 2026-02-13), and .node-version is already 26, so CI has it too. Measured on v26.7.0, q11 over a 21KB target:

brotli no dict  : 70
brotli good dict: 18

The conclusion is untouched. dcz won on decode, and neither encoder's availability was ever the axis. One of the stated reasons is what changed.

Four copies moved together

Gotcha 18's own lesson is to grep the tree when a note lands, since writing it down once left three other instances live for two days. So: CLAUDE.md twice, the build.mjs comment at the delta emitter, the roll-shell-dictionary.mjs header, and /garage/compression, which carried it in prose and in an understanding-check explanation.

The page's table was stale in a second, bigger way

Its dcz-vs-dcb table still held the pre-correction figures (79 vs 80 bytes, "about twice as fast"). The 2026-07-28 re-measure against real dictionaries never reached the page:

measure dcb dcz
all 12 shipping deltas 3,344 B 3,589 B
decode, 47,615 B rebuilt 0.1368 ms 0.0165 ms

Both inputs to the original argument were wrong, in opposite directions, and the call survived on the structural point instead: decode scales with the RECONSTRUCTION rather than the delta, so a smaller dcb delta never shrinks the decode gap. The quiz's correct answer restated the old figures verbatim and now states that.

One new rule

Three runtimes disagree on this single option, so: probe dictionary support, never infer it. node 26 honours it, workerd accepts it for zstd and silently ignores it, and bun does the same through 1.3.14 with the fix landing in oven-sh/bun#34427 (unreleased as of 1.3.14; verified here on 1.4.0-canary.1). The failure is silent every time, because a frame compressed WITHOUT the dictionary still decodes fine WITH it, so the only signal is a byte count that never shrank. The four-line control is in the note.

Verification

npm test 206/206, npm run build clean (134 page deltas, 29 quiz payloads byte-equal), npm run pages:check ok, npm run perf-budget green. The .md twin regenerates from source, so no hand-edited twin.

🤖 Generated with Claude Code

…imit

Gotcha 14 said `node:zlib`'s brotli has NO dictionary parameter, listed the
nine params as the whole list, and rested part of the dcz-over-dcb case on dcb
needing the `brotli` CLI back in the build path. Node 26 takes a `dictionary`
option (nodejs/node#61763, merged 2026-02-13) and `.node-version` is already
26, so CI has it too. Measured on v26.7.0, q11 over a 21KB target: 70 bytes
with no dictionary against 18 with one.

The CONCLUSION is untouched, because dcz won on decode and neither encoder's
availability was ever the axis. What changed is one of the stated reasons.

Four copies of the claim moved together, per gotcha 18's own lesson about
grepping the tree when a note lands: CLAUDE.md twice, the build.mjs comment at
the delta emitter, the roll-shell-dictionary.mjs header, and /garage/compression,
which carried it in prose AND in an understanding-check explanation.

While in that page, its dcz-vs-dcb table still held the pre-correction figures
(79 vs 80 bytes, "about twice as fast"). The 2026-07-28 re-measure against real
dictionaries never reached it: dcb is 245 bytes ahead across all 12 pairs (6.8%,
winning 11 of 12) and dcz decodes 8.3x faster, not 2x. Both inputs to the
original argument were wrong, in opposite directions, and the call survived on
the structural point instead: decode scales with the RECONSTRUCTION, not the
delta, so a smaller dcb delta never shrinks the decode gap. The quiz's correct
answer restated the old figures verbatim and now states that.

Adds one general rule, since three runtimes disagree on this single option:
probe dictionary support, never infer it. node 26 honours it, workerd accepts
it for zstd and silently ignores it, and bun does the same through 1.3.14 with
the fix landing in oven-sh/bun#34427 (unreleased; verified on 1.4.0-canary.1).
The failure is silent every time, because a frame compressed WITHOUT the
dictionary still decodes fine WITH it, so the only signal is a byte count that
never shrank.

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 e5136ca Aug 10 2026, 04:05 PM

@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown

Wire-size diff vs merge base

e0e447a0c7300e

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

Total Brotli: 470.81 KiB → 471.25 KiB (+0.44 KiB, +0.1%) across 46 files, 1 changed.

file base head Δ
garage/compression.html 9.12 KiB 9.56 KiB +0.44 KiB (+4.8%)

Worker bundle

No change. 268.62 KiB gzip.

Dictionary deltas (pd/)

134 → 134 deltas, 387.00 KiB → 388.99 KiB (+1.99 KiB).

@oddharsh
oddharsh enabled auto-merge (squash) August 10, 2026 16:04
@oddharsh
oddharsh merged commit 0a62f5c into main Aug 10, 2026
5 of 7 checks passed
@oddharsh
oddharsh deleted the docs/brotli-dictionary-node-26 branch August 10, 2026 16:05
oddharsh added a commit that referenced this pull request Aug 10, 2026
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 added a commit that referenced this pull request Aug 10, 2026
…d 2x faster (#307)

* feat(build): a control for bun, which builds this byte-identically and 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>

* docs: gotcha 27 gains its cleanest control, a prose-only PR failing it

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>

---------

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