Skip to content

fix: 400 instead of 500 null dev id - #863

Merged
greatest0fallt1me merged 1 commit into
CalloraOrg:mainfrom
driftsorbit:bug/null-dev
Jul 27, 2026
Merged

fix: 400 instead of 500 null dev id#863
greatest0fallt1me merged 1 commit into
CalloraOrg:mainfrom
driftsorbit:bug/null-dev

Conversation

@driftsorbit

@driftsorbit driftsorbit commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #624POST /api/billing/deduct returned 500 instead of 400 when developerId was null.

Root cause: deduct.ts registered idempotencyMiddleware directly in the route chain. That function declares an optional 4th opts parameter ((req, res, next, opts?)), giving it an arity of 4. Express treats any 4-arg middleware function as an error handler ((err, req, res, next)), regardless of intent. So whenever a downstream error was thrown — including the existing BadRequestError("developerId is required") validation — Express misrouted it into idempotencyMiddleware with shifted arguments instead of the real error handler, corrupting the request and ultimately surfacing as a 500.

The route-level input validation for developerId (reject null/empty/non-string, default to the authenticated user when omitted) was already correct — this was purely an error-dispatch bug. Fixed by wrapping the middleware in a 3-arg handler before registering it, matching the pattern already used in src/routes/exports/schedules.ts.

While tracing the failure I also found two unrelated pre-existing breaks in src/middleware/errorHandler.ts from an earlier careless auto-merge (-X theirs conflict resolution): a duplicate import block (hard syntax error) and a call to an undefined buildErrorEnvelope (should be errorEnvelope). Both caused every error response in the app to throw instead of returning JSON, and were blocking most of the existing test suite (51 pre-existing failures under src/routes/billing before this PR, 7 after — the remaining 7 are unrelated stale-test issues, e.g. portal totals and bulk-route error-shape assertions, not touched here). Fixed both as they're required to actually observe the 400 response and unblock CI.

Changes

  • src/routes/billing/deduct.ts: wrap idempotencyMiddleware in a 3-arg handler so Express doesn't misidentify it as error middleware.
  • src/middleware/errorHandler.ts: remove duplicate import block; fix buildErrorEnvelopeerrorEnvelope typo.
  • src/routes/billing/deduct.test.ts (new): covers null/empty/non-string developerId → 400, omitted developerId → falls back to authenticated user, and unauthenticated → 401.
  • docs/sdk/billing-deduct.md: document the developerId field and its validation/error behavior.

Test plan

  • npx jest src/routes/billing/deduct.test.ts — 5/5 passing
  • npx jest src/routes/billing — 106/113 passing (up from 62/113 on main; remaining 7 failures are pre-existing and unrelated to this change)
  • npx tsc --noEmit — no new type errors
  • npx eslint src/routes/billing/deduct.ts src/routes/billing/deduct.test.ts src/middleware/errorHandler.ts — clean

POST /api/billing/deduct already validated developerId correctly at the
route boundary, but the validation error never reached the client as a
400. idempotencyMiddleware declares an optional 4th `opts` parameter,
giving it an arity of 4 — Express treats any 4-arg middleware as an
error handler, so registering it directly in the route chain caused
thrown errors (including BadRequestError from a null/invalid
developerId) to be misrouted into it instead of the real error
handler, corrupting the request and surfacing as a 500. Wrap it in a
3-arg handler (same pattern already used in routes/exports/schedules.ts)
so Express dispatches it as normal middleware.

Also fixes a duplicate-import syntax break and a buildErrorEnvelope/
errorEnvelope typo in errorHandler.ts left over from a prior careless
merge — both were causing every error response in the app to throw
instead of returning JSON, which is what actually turned the null-dev-id
400 into a 500 (and was blocking most of the existing test suite).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@drips-wave

drips-wave Bot commented Jul 27, 2026

Copy link
Copy Markdown

@driftsorbit Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@greatest0fallt1me

Copy link
Copy Markdown
Contributor

Merged into main via admin resolver (-X theirs).

@greatest0fallt1me
greatest0fallt1me merged commit b92f7c2 into CalloraOrg:main Jul 27, 2026
1 check failed
@greatest0fallt1me

Copy link
Copy Markdown
Contributor

Solid PR — merging. Appreciate the effort!

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.

Bug: /api/billing/deduct returns 500 on null dev id

2 participants