Public: anyone can read this; the crew can change it.
Every compressed response says Vary: Accept-Encoding: /llms.txt and /sitemap.xml come back br-encoded without it #8340
Check · Review · Land · Live done — Landed and live in production.
`/llms.txt`, `/sitemap.xml`, `/robots.txt` and the Atom feeds now always carry `Vary: Accept-Encoding`, and are br/gzip-encoded only when the request asks for it.
shipped production is serving it deploy 7cfd21c
/llms.txt, /sitemap.xml, /robots.txt and the Atom feeds now always carry Vary: Accept-Encoding, and are br/gzip-encoded only when the request asks for it.
Cause. On production the br encoding comes from the edge (Cloudflare), not the forge. compress in src/http/compress.ts skipped any content type not in its TYPES set. So the forge sent these bodies plain with no Vary, and the edge then encoded them without adding one.
Fix. TYPES now includes text/markdown, text/plain, text/xml, application/xml, application/atom+xml and application/rss+xml, so one function owns the rule. The existing logic in compress still applies to them:
Varyis added once and merged with any existing value.- Bodies under
MIN_BYTES, HEAD requests, 206s,no-transform, and bodies that already havecontent-encodingare left alone. - It runs before
securityFloor, so the security headers are unchanged.
Adding text/plain buffers no stream: proxied app responses go through proxyApp and never reach compress, and the forge's own text/plain bodies (log text, raw blobs, metrics, errors) are already fully in memory. Log follows are text/event-stream, which is still excluded.
Tests.
test/compress-serve.test.tsrequests/llms.txtand/sitemap.xmlwithaccept-encoding: identityand withbr. Both answers carryVary: Accept-Encodingexactly once and keepnosniff. Only thebranswer hascontent-encoding: br, and it decodes to the plain body. A body underMIN_BYTESmust come back unencoded. Both tests fail with the oldcompress.ts.- Changed test, which was wrong:
test/feeds.test.ts:187expectedVaryto be exactlyauthorization, cookie. It now expectsauthorization, cookie, Accept-Encoding, the merge the issue asks for.
Checks run. bun scripts/gate.ts and bun x tsc --noEmit pass. scripts/fast.ts named 1069 test files (an estimated 93 minutes), so I ran instead the 64 test files that mention content-encoding, vary, llms/sitemap/robots, feeds, metrics, raw blobs or text/plain/text/markdown. All pass after the feeds.test.ts fix.
To try it: curl -s -o /dev/null -D - -H 'accept-encoding: br' <preview>/llms.txt shows vary: Accept-Encoding and content-encoding: br. With -H 'accept-encoding: identity' it shows vary and no encoding.
🤖 Generated with Claude Code
Clean. The fix extends the one existing rule in compress.ts. Preview /llms.txt gives vary: Accept-Encoding with br encoding when asked and plain on identity. Security headers are unchanged because compress runs before securityFloor. The changed feeds test was wrong and the change is justified. Non-blocking: the else branch in the new test is only reachable for a body under MIN_BYTES, so it is weak there. Both paths are 7 KB-plus today, so it asserts the encoded case.
/llms.txt,/sitemap.xml,/robots.txtand the Atom feeds now always carryVary: Accept-Encoding, and are br/gzip-encoded only when the request asks for it.Cause. On production the
brencoding comes from the edge (Cloudflare), not the forge.compressinsrc/http/compress.tsskipped any content type not in itsTYPESset. So the forge sent these bodies plain with noVary, and the edge then encoded them without adding one.Fix.
TYPESnow includestext/markdown,text/plain,text/xml,application/xml,application/atom+xmlandapplication/rss+xml, so one function owns the rule. The existing logic incompressstill applies to them:Varyis added once and merged with any existing value.MIN_BYTES, HEAD requests, 206s,no-transform, and bodies that already havecontent-encodingare left alone.securityFloor, so the security headers are unchanged.Adding
text/plainbuffers no stream: proxied app responses go throughproxyAppand never reachcompress, and the forge's owntext/plainbodies (log text, raw blobs, metrics, errors) are already fully in memory. Log follows aretext/event-stream, which is still excluded.Tests.
test/compress-serve.test.tsrequests/llms.txtand/sitemap.xmlwithaccept-encoding: identityand withbr. Both answers carryVary: Accept-Encodingexactly once and keepnosniff. Only thebranswer hascontent-encoding: br, and it decodes to the plain body. A body underMIN_BYTESmust come back unencoded. Both tests fail with the oldcompress.ts.test/feeds.test.ts:187expectedVaryto be exactlyauthorization, cookie. It now expectsauthorization, cookie, Accept-Encoding, the merge the issue asks for.Checks run.
bun scripts/gate.tsandbun x tsc --noEmitpass.scripts/fast.tsnamed 1069 test files (an estimated 93 minutes), so I ran instead the 64 test files that mentioncontent-encoding,vary, llms/sitemap/robots, feeds, metrics, raw blobs ortext/plain/text/markdown. All pass after thefeeds.test.tsfix.To try it:
curl -s -o /dev/null -D - -H 'accept-encoding: br' <preview>/llms.txtshowsvary: Accept-Encodingandcontent-encoding: br. With-H 'accept-encoding: identity'it showsvaryand no encoding.🤖 Generated with Claude Code
Closes #8328