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

Every wrong turn answers in the app's own voice: 404, 405, 413, 429 and 500 #13

stopped no changes pushed: API Error: Can't reach the API server — check your internet or DNS (EAI_AGAIN) (api_error) opened this
opened this

A browser that mistypes a path gets not found as bare plain text today, and an unsupported method gets a 404 that says nothing about what is allowed. Every refusal should look like this app and tell a script the same thing in JSON.

What done looks like

  • [ ] src/http.ts gains one problem(request, status, message, extra?) that answers HTML for a browser (accept: prefers text/html) and {error, message} JSON for anything else, with the security floor on both and no stack, path or runtime detail in either.
  • [ ] The HTML form renders inside the app's layout — src/pages/layout.ts if the pull for #2 has merged, src/page.ts if it has not — with the title, one sentence, and a link back to /. No new page module.
  • [ ] src/server.ts uses it for: no route (404), a path that exists under another method (405, with an Allow header naming the methods that do work), a cross-origin write (403), a body over the cap (413) and too many writes (429, with retry-after in seconds from the bucket). The error(...) handler answers 500 the same way and still logs the detail to the server log only.
  • [ ] match() in src/routes.ts grows a way to ask which methods a path supports, so the 405 is honest rather than guessed; it stays a pure function over the route table.
  • [ ] HEAD still answers exactly like GET, including for these statuses, and carries no body.
  • [ ] A 429 names the wait without leaking another person's usage, and the rate limiter's numbers do not move in this issue.
  • [ ] Tests in src/server.test.ts: GET /nope from a browser (accept: text/html) is 404 HTML carrying the layout's title; the same path with accept: application/json is 404 JSON; DELETE / is 405 with Allow: GET; a cross-origin POST is 403 in the caller's shape; an over-cap body is 413; the bucket exhausted gives 429 with retry-after; every one of them still carries content-security-policy and x-content-type-options.
  • [ ] bun test is green and no route's happy path changes.

Files I expect to change: src/http.ts, src/server.ts, src/routes.ts, src/server.test.ts, and one import from whichever module holds layout.

Risks: an error page is the easiest place to leak — render nothing from the request but the status, never the path the person asked for, and never an exception's message. Keep problem out of the action layer: a Failure from an action still goes through answer, and this is only for what never reached one. If pull #12 lands first, rebase onto src/pages/layout.ts rather than reviving src/page.ts.

Fit: Hearth — the panel that tells you plainly what happened. A person assigns; I do not.

commented

API Error: Can't reach the API server — check your internet or DNS (EAI_AGAIN)

commented

Interrupted, not the work (outage; strike cleared 2026-10-02) — was: Parked by the crew: the run ended without a pull: no changes pushed: API Error: Can't reach the API server — check your internet or DNS (EAI_AGAIN) (api_error)

commented

API Error: Can't reach the API server — check your internet or DNS (EAI_AGAIN)

commented

Interrupted, not the work (outage; strike cleared 2026-10-02) — was: Parked by the crew: the run ended without a pull: no changes pushed: API Error: Can't reach the API server — check your internet or DNS (EAI_AGAIN) (api_error)

commented

Amendment from the planner, with today's evidence. Fetched from production just now:

$ curl -s -D- -H 'accept: text/html' https://quorum--forge-trevato-apps.open-platform.sh/nope HTTP/2 404 content-type: text/plain; charset=utf-8 content-length: 9 not found

So the body is still nine bytes of plain text for a browser, and the issue stands exactly as written. Two things have settled since it was filed and resolve its open questions: #12 has merged, so layout lives in src/pages/layout.ts — use it and do not revive src/page.ts; and #20 has merged, so the 404 page should sound like the first-visit page rather than inventing a second voice.

Added criterion: the HTML refusal carries cache-control: no-store, so a CDN never caches one person's 403 or 429 for the next person. The floor headers are already on the 404 above — keep them there and let #23's route-table test be the thing that proves it, rather than writing a second list here.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs