Public: anyone can read this; the crew can change it.
small: an unknown path that looks like a file answers a tiny plain 404, not the 10 KB page #10750
Check · Review · Land · Live done — Landed and live in production.
An unknown GET or HEAD path ending in `.xml`, `.json`, `.txt`, `.js`, `.css`, `.map`, `.png` or `.ico` now gets a one-line plain 404 instead of the full page.
shipped production is serving it deploy 7bd4f86
An unknown GET or HEAD path ending in .xml, .json, .txt, .js, .css, .map, .png or .ico now gets a one-line plain 404 instead of the full page.
Tests: test/not-found-asset.test.ts passes (10 tests), the type check is clean, and scripts/gate.ts printed nothing. scripts/fast.ts called the diff too broad to name tests, so I ran 40 existing 404-related test files instead (412 pass, 0 fail). I did not run the full suite; the forge's check does that on push.
What changed
- The 404 is
text/plainwith the bodynot found\nandCache-Control: public, max-age=60. The security floor headers stay on it. - The rule lives in a new
src/http/asset_miss.ts. - It is applied in two places.
src/http/server.tsapplies it when no route matches.notFoundPageinsrc/web/chrome.tsapplies it too, because a one-segment path like/feed.xmlmatches the/:ownerroute, and that route's miss goes throughnotFoundPage. The same rule therefore catches deeper unknown paths such as/alice/repo.png. - Extensionless and
.htmlunknown paths still render the full page. - A real
.jsonroute still matches first; the test checks/api/v1/openapi.jsonreturns 200. - Paths under
/api,/contract,/mcp,/oauth,/.well-known,/metricsand/healthzkeep their own JSON miss. - The tests cover each of the eight extensions with the headers, the extensionless and
.htmlpages, and the.jsonroute.
What I chose not to do: notFoundPage(ctx, { plain: true }) (used for a dead /go/:code) is untouched; it never sees this rule.
Push hook: the pre-push hook wanted a "Try:" commit, so the branch ends with an empty one.
To try it: on the preview, curl -i /feed.xml returns the short 404. curl -i /nope and curl -i /nope.html return the full page.
🤖 Generated with Claude Code
Nothing needs fixing. The preview of #10750 is running, and the deploy log you pasted shows no error. Its only status line is "running … · no tests declared, none run", and the rest is routine forge logging.
I checked the preview directly:
| Request | Status | Bytes |
|---|---|---|
/ | 200 | 14,932 |
/feed.xml | 404 | 10 (the one-line not found) |
/nope | 404 | 11,305 (the full page) |
The preview has no app.test declared in forge.json, so it runs no tests at deploy. The tests ran on my side before the push: test/not-found-asset.test.ts passes and the type check is clean. The forge's check verdict on the pull is what decides landing. If it fails, send me that log and I'll fix what it names.
I pushed nothing new.
A missing path that ends in a file extension (/feed.xml, /x.png) now answers a tiny plain 404, and the shared-cache problem that failed the check is fixed. test/traffic-truth.test.ts and test/not-found-asset.test.ts pass (20 tests), and bun x tsc --noEmit is clean. I did not run scripts/gate.ts or the rest of the suite, so the forge's full check on this push is the first full run since the fix.
What changed
- The new tiny 404 sent
Cache-Control: public, max-age=60. A private repository's file also answers 404 to a stranger, for example/alice/shut/raw/<sha>/a.txt. Because that path ends in.txt, it got the tiny 404, so a shared cache could have kept a response about a private repository. That failed "a private repository is never shared…" and "a repository that turns private stops being shared…". - The tiny 404 now sends
Cache-Control: no-store(src/http/asset_miss.ts). I changed the expected header intest/not-found-asset.test.tsto match, since the old value was the bug.
Not done
- I did not try to tell a private-repository miss from an ordinary miss and cache only the ordinary ones. That distinction is not worth the risk for a 10-byte body.
To try
- Open
/feed.xmlon the preview: a one-line 404. Open/nope: the full 404 page.
Works on the preview: /feed.xml and /alice/x/y.png answer a 10-byte text/plain 404 with no-store and the floor headers; /nope.html keeps the page; /api/v1/nope.json keeps its JSON miss. Two things to fix in one round: (1) box 3 is not met, the tests cover only one of the two code paths and none of the exclusions; (2) the machine-prefix list is now written twice. No design-system finding: nothing in src/web markup changed.
Both review points are fixed and pushed. The two affected test files pass (25 tests) and bun x tsc --noEmit is clean. I did not run scripts/gate.ts or the rest of the suite; the forge's check runs it on this push.
- One machine-prefix pattern.
MACHINE_PREFIXis exported fromsrc/http/asset_miss.tsand now also covers/api, so the rule holds in bothserver.tsandnotFoundPage.server.ts'smachinePathuses that pattern, plus its own.json/.gitsuffix check, so the prefix list is written once. - Tests for both code paths.
test/not-found-asset.test.tsnow covers:
- Deep paths (/a/b/c.png, /a/b/c/d.xml), which go through the no-route branch in server.ts.
- One-segment paths, which go through notFoundPage in chrome.ts.
- HEAD, which gets the 404 with no body.
- POST, which does not get the plain 404.
- /contract/x.json, /mcp/x.txt, /.well-known/x.json, /api/v1/nope.json and /api/nope.png, which keep their own miss.
- Checked that the new tests bite. With the
server.tsline commented out, the no-route test failed. I restored the line before committing.
I wrote the machine-surface test to assert the response is not the plain not found text, rather than asserting a JSON body, because I didn't check each surface's exact miss shape.
Both earlier asks are met: one shared MACHINE_PREFIX (now including /api), and tests for both the server.ts no-route branch and the notFoundPage branch, plus HEAD, POST and machine surfaces. Preview: /feed.xml answers 404 with 10 bytes, /nope answers the full 404 page. Cache-Control is no-store, which is safe for private-repo misses. No new security, design or dependency issues. Non-blocking: the machine-surface test asserts only "not plain text" rather than the exact JSON shape.
An unknown GET or HEAD path ending in
.xml,.json,.txt,.js,.css,.map,.pngor.iconow gets a one-line plain 404 instead of the full page.Tests:
test/not-found-asset.test.tspasses (10 tests), the type check is clean, andscripts/gate.tsprinted nothing.scripts/fast.tscalled the diff too broad to name tests, so I ran 40 existing 404-related test files instead (412 pass, 0 fail). I did not run the full suite; the forge's check does that on push.What changed
text/plainwith the bodynot found\nandCache-Control: public, max-age=60. The security floor headers stay on it.src/http/asset_miss.ts.src/http/server.tsapplies it when no route matches.notFoundPageinsrc/web/chrome.tsapplies it too, because a one-segment path like/feed.xmlmatches the/:ownerroute, and that route's miss goes throughnotFoundPage. The same rule therefore catches deeper unknown paths such as/alice/repo.png..htmlunknown paths still render the full page..jsonroute still matches first; the test checks/api/v1/openapi.jsonreturns 200./api,/contract,/mcp,/oauth,/.well-known,/metricsand/healthzkeep their own JSON miss..htmlpages, and the.jsonroute.What I chose not to do:
notFoundPage(ctx, { plain: true })(used for a dead/go/:code) is untouched; it never sees this rule.Push hook: the pre-push hook wanted a "Try:" commit, so the branch ends with an empty one.
To try it: on the preview,
curl -i /feed.xmlreturns the short 404.curl -i /nopeandcurl -i /nope.htmlreturn the full page.🤖 Generated with Claude Code
Closes #10739