Skip to content

🐛 fix(api): return 400 with clear messages for invalid request bodies - #100

Merged
marcelsamyn merged 1 commit into
mainfrom
fix/request-validation-400
Sep 23, 2026
Merged

marcelsamyn merged 1 commit into
mainfrom
fix/request-validation-400

Conversation

@marcelsamyn

Copy link
Copy Markdown
Owner

What and why

Petals' getNode tool sent a source ID (src_…) as nodeId. POST /node/sources threw a raw ZodError, and Nitro returned it as an unhandled 500 with no useful body. This happened on every route, because each one parsed its body with a bare schema.parse(await readBody(event)).

  • parseRequestBody(schema, body) (src/lib/request-body.ts): turns a validation failure into 400 Invalid request body. The message lists each problem with its field path (z.prettifyError), and data.issues holds the Zod issues. All 81 body-parsing routes now use it. I made this change with an ast-grep codemod. sources/identity/lifecycle used readValidatedBody before; it now uses the same helper.
  • typeIdSchema errors name the wrong ID kind. For a source ID passed as a node ID, the error is: Expected a node ID starting with "node_", but received a source ID. Use a source operation for this ID instead. The prefix check stops validation, so the caller gets one clear issue and not also a length error. The MCP tools use the same schemas, so they get this message too.
  • Response-schema .parse calls are unchanged. A response that does not match its schema is a server bug and should stay a 500.

How to test

pnpm exec vitest run src/node-sources-route.test.ts src/types/typeid.test.ts

Or send POST /node/sources with {"userId":"…","nodeId":"src_01m34wpw4fesks6rketcme5875"}. The response is a 400 that includes the message above.

Status

  • pnpm run build:check (tsc + structured-output schema check): pass
  • pnpm run lint, pnpm run format, pnpm run build-sdk, pnpm run build: pass
  • Full vitest run with Postgres on :5431: 968 passed, 1 test and 2 files failed. All three failures also happen on a clean main: the tests have no DATABASE_URL or MEMORY_OPENAI_* environment variables (partition-reclassification, query/change-feed, email-request-matching.integration).
  • New tests: the typeid messages, and an HTTP-level 400 on /node/sources. I updated the change-feed and identity lifecycle route tests to expect the new error shape.
  • docs/sdk-consumer-migration.md has a new entry for the changed error behavior.

🤖 Generated with Claude Code

https://claude.ai/code/session_01FNR7YyDx7o2aCjAY4BPbEu

Routes parsed bodies with a bare `schema.parse`, so any invalid input
surfaced as an unhandled 500. `parseRequestBody` turns validation
failures into a 400 that lists each problem by field.

ID fields now name the kind of ID a caller passed by mistake, e.g. a
source ID sent as `nodeId`, so an assistant can correct the call.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FNR7YyDx7o2aCjAY4BPbEu
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@marcelsamyn
marcelsamyn merged commit b838d65 into main Sep 23, 2026
1 check passed
@marcelsamyn
marcelsamyn deleted the fix/request-validation-400 branch September 23, 2026 07:02
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.

1 participant