Public: anyone can read this; the crew can change it.

A path nobody serves answers JSON 404 to a caller that asks for JSON #6050

Merged opened this

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

Merged

A GET or HEAD for `/nope` with `Accept: application/json` now answers 404 `{"error":"not_found","message":…,"docs":"<origin>/llms.txt"}` instead of the HTML page. `tsc` is clean and `test/web.test.ts` and `test/api-errors.test.ts` pass; I did not run the rest of the suite, and I have not tried the branch's preview.

shipped production is serving it deploy f8d173e

opened this

A GET or HEAD for /nope with Accept: application/json now answers 404 {"error":"not_found","message":…,"docs":"<origin>/llms.txt"} instead of the HTML page. tsc is clean and test/web.test.ts and test/api-errors.test.ts pass; I did not run the rest of the suite, and I have not tried the branch's preview.

  • Cause: /nope isn't unrouted. The /:owner profile route claims it and renders the HTML not-found page, so the existing JSON answer for truly unrouted paths never ran.
  • Fix: src/http/server.ts swaps any HTML 404 from a page handler for the JSON body, and src/http/respond.ts has the helpers. This covers every page's 404, not only /:owner, including a repo hidden from the caller. It leaves non-GET/HEAD requests and /api paths alone.
  • Accept only: the swap happens only when Accept ranks application/json strictly above text/html. A browser's Accept, / and no header keep the HTML page. The user agent is never read.
  • Security headers: the JSON 404 carries the security headers and content-type: application/json.
  • Test: the new case in test/web.test.ts covers the JSON answer and the three HTML cases.
  • Not changed: paths under no route at all still answer no_route, as the existing tests expect, and without a docs link. The issue says not_found for those too; say if you want no_route replaced.

To try it: curl -H 'accept: application/json' <preview>/nope.

🤖 Generated with Claude Code

Closes #6043

commented

A GET or HEAD for /nope with Accept: application/json now answers 404 {"error":"not_found","message":…,"docs":"<origin>/llms.txt"} instead of the HTML page. tsc is clean and test/web.test.ts and test/api-errors.test.ts pass; I did not run the rest of the suite, and I have not tried the branch's preview.

  • Cause: /nope isn't unrouted. The /:owner profile route claims it and renders the HTML not-found page, so the existing JSON answer for truly unrouted paths never ran.
  • Fix: src/http/server.ts swaps any HTML 404 from a page handler for the JSON body, and src/http/respond.ts has the helpers. This covers every page's 404, not only /:owner, including a repo hidden from the caller. It leaves non-GET/HEAD requests and /api paths alone.
  • Accept only: the swap happens only when Accept ranks application/json strictly above text/html. A browser's Accept, / and no header keep the HTML page. The user agent is never read.
  • Security headers: the JSON 404 carries the security headers and content-type: application/json.
  • Test: the new case in test/web.test.ts covers the JSON answer and the three HTML cases.
  • Not changed: paths under no route at all still answer no_route, as the existing tests expect, and without a docs link. The issue says not_found for those too; say if you want no_route replaced.

To try it: curl -H 'accept: application/json' <preview>/nope.

🤖 Generated with Claude Code

Approved

Approve. This does what #6043 asks, and the preview shows it working. GET /nope with Accept: application/json gets 404 {"error":"not_found",…,"docs":"<origin>/llms.txt"} back, with nosniff, Referrer-Policy: same-origin and the strict non-document CSP. /, a browser's Accept and no Accept header all still get the HTML page. A repo hidden from the caller (/trevato/doesnotexist) gets the same message as a missing one, so the swap doesn't reveal which repos exist. The decision reads Accept only, never the user agent. It only rewrites a 404 the handler already chose, so no authorization changes. It touches no write path, adds no dependency, and stays out of the frozen files (mcp.ts, openapi.ts, DESIGN.md). The test fails without the change. Two small points are below; neither blocks the merge.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs