The Symptom: “undefined undefined undefined”
I spent days debugging this error:
Error: undefined undefined: undefined at callErrorFromStatus at onReceiveStatus at makeUnaryRequestOr sometimes:
Exception occurred in retry method that was not classified as transientEverything looked correct:
- Pub/Sub initialized successfully
- Topics existed
- IAM permissions were correct
gcloudCLI worked perfectly- A minimal Node.js script workedmmediately
Yet my Bun app kept failing.
The Setup
Runtime: Bun
Library: @google-cloud/pubsub
Transport: default (gRPC)
Environment: Docker + local dev
Credentials: ADC + service account JSON
The client initialization looked textbook:
this.pubsub = new PubSub({ projectId: config.pubsub.projectId,});This is exactly what Google’s documentation recommends.
And yet… it failed. Every time.
The Breakthrough: fallback: true
Out of desperation, after reading some Bun issues, I tried this:
this.pubsub = new PubSub({ projectId: config.pubsub.projectId, fallback: true,});Suddenly:
- Messages published successfully
- Retries worked
- No more “undefined undefined: undefined”
- No more gRPC stack traces
One flag. Everything worked.
So what was happening?
What fallback: true Actually Does
Google Pub/Sub supports two transports:
- gRPC (default) — Fast, binary, HTTP/2
- REST/HTTP+JSON — Slower, more compatible
When you set fallback: true, you’re saying:
“Do NOT use gRPC. Use REST instead.”
That single boolean switches the entire transport layer.
Why gRPC Breaks in Bun
The Core Issue
Bun does not fully support Node’s native gRPC stack.
Google Pub/Sub depends on:
@grpc/grpc-js- HTTP/2 primitives
- Low-level socket handling
- Node-specific stream internals
Bun:
- Implements many Node APIs
- But not all edge cases
- Especially around HTTP/2 + gRPC interactions
Result: Things break silently.
gRPC Errors Lose Their Metadata
Instead of a proper error like:
PERMISSION_DENIEDUNAVAILABLEDEADLINE_EXCEEDEDYou get:
undefined undefined: undefinedWhy?
- gRPC status codes aren’t mapped correctly in Bun
- Bun drops or mis-parses response trailers (metadata)
- Error details never reach the client
- Retry logic says: “This error isn’t transient… but I don’t know what it is”
The result is a completely unhelpful error message.
The Failure Mode Is Silent and Misleading
This is the worst part.
From the outside, it looks like:
- Bad IAM permissions
- Missing credentials
- Broken topic
- Pub/Sub outage
But none of those are true.
Your app is fine.
Your config is fine.
Your cloud setup is fine.
The runtime is the problem.
And you won’t know it until you’ve already spent a day debugging IAM policies.
Note
The insidious part: This bug is invisible to monitoring. Your app logs say “failed to publish” without explaining why. Prometheus metrics show errors. But there’s nothing wrong with your infrastructure.
Why Node.js Works Instantly
When I ran the same code with Node:
GOOGLE_APPLICATION_CREDENTIALS=./sentinel-sa.json node test-pubsub.jsIt worked immediately. No changes. No flags.
Why?
- Node has first-class support for gRPC
- Google SDKs are tested primarily against Node
- HTTP/2 and stream semantics are stable
- Error handling is correct
This is why Google’s documentation always says:
“Node.js runtime”
—not “JavaScript runtime”.
The Performance Tradeoff
gRPC vs REST: Real Numbers
gRPC benchmarks consistently show:
- 7–10× faster throughput than REST
- 6× higher requests per second in equivalent services
- Lower CPU usage due to binary encoding and HTTP/2
What This Means
gRPC (default):
- Best for high-throughput, latency-sensitive workloads
- Works perfectly in Node.js
- Breaks mysteriously in Bun
REST fallback:
- Works in any HTTP-capable runtime
- Simple, predictable error handling
- 5–10× slower throughput
- Higher latency
For most workloads, the performance difference is acceptable. Especially for:
- Log pipelines (throughput is moderate)
- Background jobs (latency isn’t critical)
- Monitoring agents (reliability > speed)
Note
The pragmatic truth:
Using fallback: true in Bun is not “wrong.” You’re making a rational tradeoff: compatibility and correctness over raw throughput. For background services, that’s the right call.
Why Bun Isn’t Ready for Google Pub/Sub (Today)
Bun is fantastic for:
- REST APIs
- Edge services
- Frontend tooling
- Fast dev loops
But it’s not ready for:
- gRPC-heavy SDKs
- Deep cloud integrations
- Google Cloud client libraries
Specifically, these GCP services rely on gRPC:
- Pub/Sub ← Don’t use gRPC in Bun
- Bigtable ← Use fallback
- Spanner ← Use fallback
- Firestore (gRPC path) ← Use fallback
The issue isn’t unique to Pub/Sub. It’s systemic to how Bun handles gRPC.
Recommended Patterns
If You Insist on Bun
import { PubSub } from '@google-cloud/pubsub';
const pubsub = new PubSub({ projectId: process.env.GCP_PROJECT_ID, fallback: true, // REQUIRED in Bun});
const topic = pubsub.topic(process.env.PUBSUB_TOPIC_NAME!);
// Now this worksawait topic.publishMessage({ json: { message: 'hello' },});Additional considerations:
- Expect slightly higher latency (5–10×)
- Avoid very high message throughput (100k+ msgs/sec)
- Monitor publish timings
- Use for: background jobs, log pipelines, internal tools
If You’re Building Infrastructure
Use Node.js.
Seriously.
Especially if:
- Pub/Sub is core to your system
- Retries matter
- You need predictable error handling
- Throughput is important
Lessons Learned
-
Not all “Node-compatible” runtimes are cloud-SDK-compatible
- Bun passes 99% of Node tests
- But gRPC is that 1%
-
gRPC is fragile outside Node
- It’s deeply integrated with Node internals
- Other runtimes need explicit support
-
Google SDKs assume Node semantics
- Not JavaScript semantics
- There’s a difference
Final Thoughts
This bug cost me days because:
- Nothing was technically wrong
- Logs were misleading
- Failures looked like infra issues
- The solution was a single flag
But here’s the deeper lesson:
When building on cloud infrastructure, match your runtime to the platform.
Bun is a fantastic runtime. But it’s not ready for gRPC-heavy Google Cloud SDKs.
…and you want low-friction, reliable deployment:
Use Node.js.