Skip to content

Answer unknown API paths and uncaught errors with JSON - #438

Open
officialasishkumar wants to merge 1 commit into
useagenthq:mainfrom
officialasishkumar:fix/json-not-found-and-errors
Open

officialasishkumar wants to merge 1 commit into
useagenthq:mainfrom
officialasishkumar:fix/json-not-found-and-errors

Conversation

@officialasishkumar

@officialasishkumar officialasishkumar commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

By opening this pull request you agree to the Contributor License Agreement. On your first pull request a bot asks you to sign it with a one-line comment.

Fixes #215

Neither the backend app nor the gateway app set notFound or onError, so both fell back to Hono's plain-text defaults: 404 Not Found for a path no route serves, and Internal Server Error for an error no route caught. Every other error is JSON, so a client that reads the body as JSON failed a second time on these.

What changed

  • New backend/src/http-errors.ts with installJsonErrorHandlers(app, surface):
    • An unknown path answers 404 {"error":"not_found"}.
    • An error no route caught answers 500 {"error":"internal_error"} and is logged as [<surface>] <METHOD> <path> failed: followed by the error.
    • An HTTPException, which Hono middleware throws on purpose, keeps the response it carries, as Hono's default handler does.
    • The logged path goes through createSecretRedactor([]) from secrets/redact.ts. A preview view path carries a signed capability (/api/port-proxy/:threadId/view/:capability/*), and that must not reach the logs. The redactor replaces it with <redacted> and keeps the rest of the path.
  • src/index.ts installs it on the backend app (api), and src/gateway-app.ts on the gateway app (gateway). Routers mounted with app.route() set no handler of their own, so their errors and unknown paths reach these handlers too.
  • docs-site/docs/api/index.mdx: a "JSON errors" line under Conventions.

Tests

  • src/http-errors.test.ts, on a Hono app with the handlers installed:
    • unknown path: JSON 404
    • a route that throws: JSON 500 with application/json, logged once as [test] GET /api/boom failed: with the error
    • an error thrown inside a mounted router: JSON 500 with method and path
    • a capability in the path is logged as <redacted>
    • an HTTPException keeps its status and body, and nothing is logged
    • a route's own answer is untouched
  • src/gateway-app.test.ts:
    • createGatewayApp() answers an unknown path with a JSON 404.
    • A test-only route added to it that throws comes back as 500 {"error":"internal_error"}, logged as [gateway] GET /api/test-only-crash failed:.
  • test/api-errors.test.ts: through the real backend app and auth adapter, a signed-in GET /api/does-not-exist and GET /api/runs/abc/no-such-view answer 404 with application/json and {"error":"not_found"}.

The gateway and backend tests fail on main, where the answers are plain text.

Checks with Bun 1.3.14:

  • bun run typecheck: passes
  • bun test scripts/agent-docs.test.ts: 5 pass
  • cd backend && bun run test: 3569 pass, 28 fail.
    • 27 failures are in src/engines/runtime-*, codex-* and src/provider-gateway/sandbox-config.test.ts. Those files fail the same way on an unmodified main checkout on my machine, because they probe local processes, ports and the filesystem.
    • The other failure, src/runs/thread-relationship.test.ts "singular pumps once on create...", counts every console.error while it runs, and a stray log from earlier background work landed in that window once. It passes 3 of 3 runs alone on both this branch and main, and 2 of 2 runs after the files that ran before it in the suite.

End to end

I ran the backend (src/index.ts) and the gateway (src/gateway.ts) locally on Postgres 16 with pgvector, signed in, and called them with curl:

GET  :3201/api/does-not-exist           -> 404 application/json {"error":"not_found"}
GET  :3201/api/runs/abc/no-such-view    -> 404 application/json {"error":"not_found"}
GET  :3202/api/provider-typo            -> 404 application/json {"error":"not_found"}
GET  :3202/health                       -> 200 {"status":"ok","surface":"gateway"}   (unchanged)
PATCH :3201/api/tasks/abc {"title":"x"} -> 500 application/json {"error":"internal_error"}

The last call is a real unhandled error: the malformed id reaches a uuid column, which is #207. Before this change it answered plain-text Internal Server Error. The backend log now names it: [api] PATCH /api/tasks/abc failed: followed by the database error. Routes that already catch their own errors keep their answers, for example GET /api/knowledge/documents/abc still returns its own 500 {"error":"get failed"}.


Autofix with [code]smith
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.

Neither the backend app nor the gateway app set notFound or onError, so
both fell back to Hono's plain-text defaults: "404 Not Found" for a path
no route serves and "Internal Server Error" for an error no route
caught. Every other error is JSON, so a client reading the body as JSON
failed a second time on these.

installJsonErrorHandlers (src/http-errors.ts) gives both apps the same
last-resort answers: 404 {"error":"not_found"} and 500
{"error":"internal_error"}. The 500 is logged with the surface, method
and path. The path goes through the secret redactor first, since a
preview view path carries a signed capability. An HTTPException keeps
the response it carries, as with Hono's default handler. Routers mounted
with app.route() set no handler of their own, so their errors and
unknown paths reach these handlers too.

The API overview in the docs now states the error shape.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Return JSON, not plain text, for unknown API paths and crashes

1 participant