Skip to content

Return 4xx instead of 500 for client errors in bundle endpoint#676

Open
ide wants to merge 1 commit into@ide/snackager-tsc-upgradefrom
@ide/snackager-4xx-client-errors
Open

Return 4xx instead of 500 for client errors in bundle endpoint#676
ide wants to merge 1 commit into@ide/snackager-tsc-upgradefrom
@ide/snackager-4xx-client-errors

Conversation

@ide
Copy link
Copy Markdown
Member

@ide ide commented Feb 25, 2026

Stacked on #675.

Why

All errors from the /bundle/ endpoint returned HTTP 500, including permanent failures like "package not found" (404 from npm) and "package can't be bundled" (invalid modules, unresolvable dependencies). These expected client errors inflated 5xx rates and created unnecessary Sentry noise. They should be 4xx to signal "don't retry" and to separate them from real server errors in metrics.

How

Two new error classes, PackageNotFoundError and UnbundleablePackageError, are thrown from the appropriate sites in fetchMetadata and packageBundle. In bundle.ts, PackageNotFoundError maps to 404 and UnbundleablePackageError maps to 422, with descriptive response bodies explaining the error and how to work around it. Only 500s are reported to Sentry. fetchBundle stores errorName in Redis alongside cached errors so that UnbundleablePackageError is preserved across the cache boundary. Log levels in servePackage and fetchBundle are downgraded to warn for client errors.

Test Plan

yarn test passes (74 tests). New unit tests in src/__tests__/bundle.test.ts mock servePackage to throw each error class and verify the status code, response body, and Sentry behavior.

Manually tested against local Redis with NODE_OPTIONS=--openssl-legacy-provider:

  • 404 cold fetch: this-package-does-not-exist-xyz@1.0.0 returns 404 with a descriptive message. Repeated requests also return 404 since registry 404s are not cached.
  • 422 cold fetch: bcrypt@5.1.1 returns 200 pending because bundling is async. After the bundler fails with "Cannot resolve module crypto after installing it as a dependency", the UnbundleablePackageError and errorName are stored in the Redis hash. The subsequent request re-throws the error and returns 422.
  • 400: react-native@0.73.0 returns 400 (existing parseRequest behavior, unchanged).

@ide ide requested a review from byCedric as a code owner February 25, 2026 02:30
@ide ide force-pushed the @ide/snackager-4xx-client-errors branch 4 times, most recently from e078abd to 7a6212f Compare February 25, 2026 02:58
Why
===
All errors from the `/bundle/` endpoint returned HTTP 500, including permanent failures like "package not found" (404 from npm) and "package can't be bundled" (invalid modules, unresolvable dependencies). These expected client errors inflated 5xx rates and created unnecessary Sentry noise. They should be 4xx to signal "don't retry" and to separate them from real server errors in metrics.

How
===
Two new error classes, `PackageNotFoundError` and `UnbundleablePackageError`, are thrown from the appropriate sites in `fetchMetadata` and `packageBundle`. In `bundle.ts`, `PackageNotFoundError` maps to 404 and `UnbundleablePackageError` maps to 422, with descriptive response bodies explaining the error and how to work around it. Only 500s are reported to Sentry. `fetchBundle` stores `errorName` in Redis alongside cached errors so that `UnbundleablePackageError` is preserved across the cache boundary. Log levels in `servePackage` and `fetchBundle` are downgraded to warn for client errors.

Test Plan
===
`yarn test` passes (74 tests). New unit tests in `src/__tests__/bundle.test.ts` mock `servePackage` to throw each error class and verify the status code, response body, and Sentry behavior.

Manually tested against local Redis with `NODE_OPTIONS=--openssl-legacy-provider`:
- 404 cold fetch: `this-package-does-not-exist-xyz@1.0.0` returns 404 with a descriptive message. Repeated requests also return 404 since registry 404s are not cached.
- 422 cold fetch: `bcrypt@5.1.1` returns 200 pending because bundling is async. After the bundler fails with "Cannot resolve module crypto after installing it as a dependency", the `UnbundleablePackageError` and `errorName` are stored in the Redis hash. The subsequent request re-throws the error and returns 422.
- 400: `react-native@0.73.0` returns 400 (existing `parseRequest` behavior, unchanged).
@ide ide force-pushed the @ide/snackager-4xx-client-errors branch from 7a6212f to 85c52bc Compare February 25, 2026 04:14
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