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

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

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

opened this

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/plain with the body not found\n and Cache-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.ts applies it when no route matches. notFoundPage in src/web/chrome.ts applies it too, because a one-segment path like /feed.xml matches the /:owner route, and that route's miss goes through notFoundPage. The same rule therefore catches deeper unknown paths such as /alice/repo.png.
  • Extensionless and .html unknown paths still render the full page.
  • A real .json route still matches first; the test checks /api/v1/openapi.json returns 200.
  • Paths under /api, /contract, /mcp, /oauth, /.well-known, /metrics and /healthz keep their own JSON miss.
  • The tests cover each of the eight extensions with the headers, the extensionless and .html pages, and the .json route.

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

Closes #10739

commented

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/plain with the body not found\n and Cache-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.ts applies it when no route matches. notFoundPage in src/web/chrome.ts applies it too, because a one-segment path like /feed.xml matches the /:owner route, and that route's miss goes through notFoundPage. The same rule therefore catches deeper unknown paths such as /alice/repo.png.
  • Extensionless and .html unknown paths still render the full page.
  • A real .json route still matches first; the test checks /api/v1/openapi.json returns 200.
  • Paths under /api, /contract, /mcp, /oauth, /.well-known, /metrics and /healthz keep their own JSON miss.
  • The tests cover each of the eight extensions with the headers, the extensionless and .html pages, and the .json route.

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

commented

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:

RequestStatusBytes
/20014,932
/feed.xml40410 (the one-line not found)
/nope40411,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.

commented

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 in test/not-found-asset.test.ts to 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.xml on the preview: a one-line 404. Open /nope: the full 404 page.
Changes requested — before the latest push

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.

commented

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_PREFIX is exported from src/http/asset_miss.ts and now also covers /api, so the rule holds in both server.ts and notFoundPage. server.ts's machinePath uses that pattern, plus its own .json/.git suffix check, so the prefix list is written once.
  • Tests for both code paths. test/not-found-asset.test.ts now 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.ts line 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.

Approved

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.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs