fix(errors): preserve fastify statusCode in global error handler#3899
Merged
Conversation
The global setErrorHandler in src/index.ts was unconditionally coercing every uncaught error to 500. That hid two real signals on /public/v1 during the daily.dev hackathon: - Rate-limit hits (fastify-rate-limit throws a FastifyError with statusCode 429) → reported as 500 - Schema validation errors (FST_ERR_VALIDATION with statusCode 400) → reported as 500 Now we preserve any 4xx/5xx statusCode set by fastify or plugins, default to 500 otherwise. Also downgrades the log line from ERROR to WARN for 4xx so client mistakes don't trip noise alerts. Adds two regression tests under /public/v1/search/posts (missing required q parameter and invalid time enum).
|
🍹 The Update (preview) for dailydotdev/api/prod (at ec9913e) was successful. ✨ Neo ExplanationRoutine deployment of a bug fix that corrects HTTP error response codes (500 → 400 for validation errors), with standard image rollout across all workloads and migration jobs. ✅ Low RiskThis is a routine application deployment rolling out commit The two migration Jobs (DB and Clickhouse) are being cycled as expected — the old commit-suffixed Jobs are deleted and new ones for the current commit are created. This is the standard migration pattern for this stack. Resource Changes Name Type Operation
~ vpc-native-update-tag-materialized-views-cron kubernetes:batch/v1:CronJob update
~ vpc-native-calculate-top-readers-cron kubernetes:batch/v1:CronJob update
~ vpc-native-clean-stale-user-transactions-cron kubernetes:batch/v1:CronJob update
~ vpc-native-update-source-public-threshold-cron kubernetes:batch/v1:CronJob update
~ vpc-native-user-profile-analytics-clickhouse-cron kubernetes:batch/v1:CronJob update
~ vpc-native-clean-gifted-plus-cron kubernetes:batch/v1:CronJob update
~ vpc-native-daily-digest-cron kubernetes:batch/v1:CronJob update
~ vpc-native-update-highlighted-views-cron kubernetes:batch/v1:CronJob update
~ vpc-native-update-trending-cron kubernetes:batch/v1:CronJob update
~ vpc-native-private-deployment kubernetes:apps/v1:Deployment update
~ vpc-native-clean-zombie-opportunities-cron kubernetes:batch/v1:CronJob update
~ vpc-native-update-current-streak-cron kubernetes:batch/v1:CronJob update
~ vpc-native-sync-subscription-with-cio-cron kubernetes:batch/v1:CronJob update
~ vpc-native-user-posts-analytics-refresh-cron kubernetes:batch/v1:CronJob update
~ vpc-native-channel-digests-cron kubernetes:batch/v1:CronJob update
~ vpc-native-post-analytics-history-day-clickhouse-cron kubernetes:batch/v1:CronJob update
~ vpc-native-expire-super-agent-trial-cron kubernetes:batch/v1:CronJob update
~ vpc-native-channel-highlights-cron kubernetes:batch/v1:CronJob update
~ vpc-native-clean-zombie-images-cron kubernetes:batch/v1:CronJob update
~ vpc-native-clean-zombie-users-cron kubernetes:batch/v1:CronJob update
~ vpc-native-worker-job-deployment kubernetes:apps/v1:Deployment update
~ vpc-native-update-tags-str-cron kubernetes:batch/v1:CronJob update
~ vpc-native-ws-deployment kubernetes:apps/v1:Deployment update
- vpc-native-api-clickhouse-migration-1c932a10 kubernetes:batch/v1:Job delete
- vpc-native-api-db-migration-1c932a10 kubernetes:batch/v1:Job delete
~ vpc-native-update-achievement-rarity-cron kubernetes:batch/v1:CronJob update
~ vpc-native-clean-channel-highlights-cron kubernetes:batch/v1:CronJob update
~ vpc-native-bg-deployment kubernetes:apps/v1:Deployment update
~ vpc-native-clean-zombie-user-companies-cron kubernetes:batch/v1:CronJob update
~ vpc-native-clean-expired-better-auth-sessions-cron kubernetes:batch/v1:CronJob update
~ vpc-native-temporal-deployment kubernetes:apps/v1:Deployment update
~ vpc-native-clean-old-notifications-cron kubernetes:batch/v1:CronJob update
+ vpc-native-api-db-migration-ee2d5dc6 kubernetes:batch/v1:Job create
~ vpc-native-squad-posts-analytics-refresh-cron kubernetes:batch/v1:CronJob update
~ vpc-native-rotate-daily-quests-cron kubernetes:batch/v1:CronJob update
~ vpc-native-hourly-notification-cron kubernetes:batch/v1:CronJob update
~ vpc-native-materialize-monthly-best-post-archives-cron kubernetes:batch/v1:CronJob update
+ vpc-native-api-clickhouse-migration-ee2d5dc6 kubernetes:batch/v1:Job create
~ vpc-native-user-profile-analytics-history-clickhouse-cron kubernetes:batch/v1:CronJob update
~ vpc-native-post-analytics-clickhouse-cron kubernetes:batch/v1:CronJob update
~ vpc-native-personalized-digest-deployment kubernetes:apps/v1:Deployment update
... and 12 other changes |
Global error handler now preserves fastify statusCode, so validation errors surface as 400 instead of being clobbered to 500. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Makes the global
setErrorHandlerinsrc/index.tspreserve fastify'sstatusCodefor client-side errors instead of unconditionally returning 500.Why
Investigating
/public/v1/search/{posts,tags}5xx during routine monitoring, every 500 I dug into turned out to be a non-500 in disguise. Two patterns observed:1. Rate-limit hits reported as 500
fastify-rate-limitthrows aFastifyErrorwithstatusCode: 429. The current handler ignores it and writes 500. Log line on each occurrence:Trace
response_status_codematches log timestamp 1:1, but reports 500.2. Schema validation errors reported as 500
Requests with malformed querystrings (e.g.
q[query]=...instead ofq=..., or missing requiredq) raise anFST_ERR_VALIDATIONwithstatusCode: 400:Same handler, same 500.
Net:
/public/v15xx metrics are polluted with non-server errors, and legitimate 429s (which we want to track for abuse) are invisible.Change
statusCodeset by fastify or plugins.ERRORtoWARNfor 4xx so client mistakes don't trip noise alerts.{ statusCode: 500, error: 'Internal Server Error' }body for true 5xx (no message leak).Tests
Two new regression tests on
/public/v1/search/posts:qparameter → 400 (was 500).timeenum value → 400 (was 500).Both fail on
mainand pass on this branch.Risk
{ statusCode: 500, error: 'Internal Server Error' }. Now 4xx errors include the originalerr.message. This is a small response-shape change but matches what fastify would emit by default and what the public API's own auth and rate-limit handlers already emit.