Return 4xx instead of 500 for client errors in bundle endpoint#676
Open
ide wants to merge 1 commit into@ide/snackager-tsc-upgradefrom
Open
Return 4xx instead of 500 for client errors in bundle endpoint#676ide wants to merge 1 commit into@ide/snackager-tsc-upgradefrom
ide wants to merge 1 commit into@ide/snackager-tsc-upgradefrom
Conversation
e078abd to
7a6212f
Compare
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).
7a6212f to
85c52bc
Compare
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.
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,
PackageNotFoundErrorandUnbundleablePackageError, are thrown from the appropriate sites infetchMetadataandpackageBundle. Inbundle.ts,PackageNotFoundErrormaps to 404 andUnbundleablePackageErrormaps to 422, with descriptive response bodies explaining the error and how to work around it. Only 500s are reported to Sentry.fetchBundlestoreserrorNamein Redis alongside cached errors so thatUnbundleablePackageErroris preserved across the cache boundary. Log levels inservePackageandfetchBundleare downgraded to warn for client errors.Test Plan
yarn testpasses (74 tests). New unit tests insrc/__tests__/bundle.test.tsmockservePackageto 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:this-package-does-not-exist-xyz@1.0.0returns 404 with a descriptive message. Repeated requests also return 404 since registry 404s are not cached.bcrypt@5.1.1returns 200 pending because bundling is async. After the bundler fails with "Cannot resolve module crypto after installing it as a dependency", theUnbundleablePackageErroranderrorNameare stored in the Redis hash. The subsequent request re-throws the error and returns 422.react-native@0.73.0returns 400 (existingparseRequestbehavior, unchanged).