fix: externalize gRPC/pubsub packages so bundling can't break publish#6767
Merged
Conversation
Breach-alert emails silently stopped in prod after the 2026-05-26 deploy: POST /api/v1/hibp/notify failed to publish to Pub/Sub ~100% of the time (HTTP 429), so no alerts were queued or sent. Ingestion was unaffected, so breaches still showed on the site. Root cause: @grpc/grpc-js was bundled by the Next.js build (not listed in serverExternalPackages, and not on Next's default externalized list). A protobufjs 7.5.5->7.5.8 bump in that release made the bundled grpc-js stack corrupt its gRPC StatusObject, which broke publishing and rendered the failure as the opaque "undefined undefined: undefined". Verified with a local A/B against real Pub/Sub (bundled=429, external=200) and a dependency bisect that pinned protobufjs (Next.js and grpc-js versions ruled out). Fix: add @grpc/grpc-js, google-gax and @google-cloud/pubsub to serverExternalPackages so they load unbundled from node_modules. Adds a regression test asserting they stay external.
GregHilstonMozilla
requested review from
EMMLynch,
groovecoder,
joeherm and
mansaj
July 15, 2026 02:46
GregHilstonMozilla
marked this pull request as ready for review
July 15, 2026 02:48
Collaborator
Author
There was a problem hiding this comment.
This unit test honestly seems pretty low value. I'll happily remove it if anyone else feels that way...
joeherm
reviewed
Jul 15, 2026
joeherm
left a comment
Collaborator
There was a problem hiding this comment.
Good find and really great explanation. Is it possible to test this with an E2E test against the stage environment in addition to / instead of the unit test?
Collaborator
Author
|
@joeherm I'm going to hold off on authoring an e2e test for right now, as I think it will push back resolving this issue another work day. I've added it as one of the top action items in the incident document. TY for reviewing this and brainstorming :) |
joeherm
approved these changes
Jul 15, 2026
joeherm
left a comment
Collaborator
There was a problem hiding this comment.
Fix LGTM- let's keep the regression guard in place and file a ticket to E2E test this pathway.
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.
References:
Jira: MNTOR-5314
Description
Problem
Breach alert emails silently stopped in production. HIBP POSTs new breach hash ranges to
POST /api/v1/hibp/notify, which publishes them to Pub/Sub for the breach alerts consumer to email affected users. The publish (pubsub.topic(...).publishMessage()) was failing ~100% of the time (≈ 19.7M/45d; HTTP 429 returned to HIBP), so nothing was queued, consumed, or emailed. Ingestion was unaffected, and breaches still appeared on the site, only alerting was dead.Root cause
@grpc/grpc-jswas being bundled by the Next.js build (it was not listed in serverExternalPackages). Next.js bundles server-side dependencies by default. Libraries that use Node.js native features must be marked external so they load via nativerequire(Next.js docs).Next ships a built-in allow-list of packages it auto-externalizes (
server-external-packages.jsonc). That list includes packages likefirebase-admin,pg,mongodb,dd-trace, etc., but not@grpc/grpc-js,google-gax, or@google-cloud/*, so those get bundled unless we add them.Bundling
grpc-jscorrupts its error.StatusObject:grpc-jsbuilds an error message as${code} ${Status[code]}: ${details}, so when the status fields are stripped the failure renders as the uselessundefined undefined: undefined, which is why the Sentry issues were titled uselessly and the cause stayed hidden. The same "bundling makes grpc's statusundefined" failure is reported against@google-cloud/pubsubin nodejs-pubsub#1341 (a browser-bundling context, but the same failure mode).Verified with a local A/B against real (stage) Pub/Sub using the same topic, only bundling differs. bundled → 429
undefined undefined: undefined; un bundled → 200 success (a plaingcloudpublish to the same topic also succeeds).Timeline (all UTC):
protobufjs7.5.5→7.5.8 (08715f8da) intomain, theeventual trigger, dormant until it deploys.
prod-2026.05.06image. Publishing is healthy.prod-2026.05.26deploys, carrying the protobufjs bump; the bundled grpc-js stack now corrupts the publish. Outage begins.undefined undefined: undefined), there was no publish-failure alert, and the local/CI Pub/Sub emulator doesn't reproduce it.@grpc/grpc-jsversions ruled out), and the fix (this PR) validated against real (stage) Pub/Sub.Why it wasn't caught
Local dev and CI use the Pub/Sub emulator (insecure transport); the failure only manifests against real Pub/Sub (TLS), so tests passed. The error was also opaque (
undefined undefined: undefined), and there was no alert on the publish failure ratio (the dead letter queue can't catch a pre queue failure).Fix
Add
@grpc/grpc-js,google-gax, and@google-cloud/pubsubtoserverExternalPackagesinnext.config.jsso they load unbundled fromnode_modules, where the gRPC status, and the publish, work correctly. Adds a regression test asserting they stay external.Recovery / backfill (follow up, not in this PR)
This PR stops the bleeding; it does not resend the ~7 weeks of missed alerts. HIBP does not automatically renotify, so we must retrigger. The missed breaches are enumerable (breaches added since 2026-05-26 with no
email_notificationsincident row). Two options:email_notificationsso reruns don't double send. Something we can do all by ourselves.How to test
docker compose --env-file .env.local up -d(postgres + pubsub emulator),npm run dev, then the README notify curl tohttp://localhost:6060/api/v1/hibp/notify→ HTTP 200 +queued_breach_notification_success.serverExternalPackages, restart, repeat → HTTP 429 witherror_queuing_hibp_breach: undefined undefined: undefined.npx vitest run src/app/api/v1/hibp/notify/serverExternalPackages.test.ts→ passes; fails if any of the three packages is dropped fromserverExternalPackages.Checklist (Definition of Done)
(
serverExternalPackages.test.ts). Note: a runtime bundler-config regression can't beexercised in a unit test; this guards against silently dropping the packages from the list.
A real-transport publish smoke test is tracked as a follow-up.