Problem description
CompressionFilter's IdentityHandler.writeMessage (packages/grpc-js/src/compression-filter.ts) calls message.copy(output, 5), which is a Buffer-only method:
async writeMessage(message, compress) {
const output = Buffer.allocUnsafe(message.length + 5);
output.writeUInt8(0, 0);
output.writeUInt32BE(message.length, 1);
message.copy(output, 5); // ← Buffer-only
return output;
}
Uint8Array.prototype.copy is undefined. If the caller-provided request serializer produces a Uint8Array rather than a Buffer, this throws TypeError: message.copy is not a function. The thrown error then propagates as a code: undefined, details: undefined, empty-Metadata gRPC status (the empty-status side is tracked separately: #3051).
Uint8Array request serializers are reachable in practice via protobufjs, which falls back to Uint8Array-returning Writer.alloc whenever protobufjs.util.Buffer is unset. One concrete trigger is the @protobufjs/inquire@1.1.1 regression — see protobufjs/protobuf.js#2214 — where bundlers (Turbopack, certain webpack configurations) statically rewrite the new dynamic require(name) to throw MODULE_NOT_FOUND, leaving util.Buffer = null in any bundled server build.
Reproduction steps
In a Next.js 16 application with output: "standalone" (Turbopack):
- Add
@google-cloud/firestore@8.3.0 and use it from a Server Component (any call works: collection(...).get(), doc(...).get(), etc.).
- Ensure
@protobufjs/inquire resolves to 1.1.1 (the default with protobufjs@7.5.6+).
next build and run the standalone output. Trigger any Firestore call.
Expected: the call succeeds or fails with a meaningful status.
Actual: the call's callback receives Error: undefined undefined: undefined with { code: undefined, details: undefined, metadata: Metadata { internalRepr: Map(0) {}, options: {} } }. Running with GRPC_TRACE=all GRPC_VERBOSITY=DEBUG shows cancelWithStatus code: undefined details: "undefined" firing within 1 ms of write() called with message of length N.
Independent of protobufjs, the same path is reachable by registering a custom method whose requestSerialize returns new Uint8Array([...]) and calling client.makeUnaryRequest(...).
Environment
- OS: macOS 15.6 arm64 (local repro); Linux amd64 (Google Cloud Run production)
- Node: v24
- Node installation: project
engines.node = "24", npm 11
- Package:
@grpc/grpc-js@1.13.4
- Adjacent:
google-gax@5.0.6, @google-cloud/firestore@8.3.0, @grpc/proto-loader@0.7.15, Next.js 16.2.6 with Turbopack
Additional context
Suggested fix: output.set(message, 5) in place of message.copy(output, 5). Buffer extends Uint8Array and both expose .set, so the call works for either input.
Impact in our deployment: a Dependabot bump to protobufjs@7.5.8 (which pulls @protobufjs/inquire@1.1.1 transitively) caused this TypeError on every Firestore call. Under google-gax retry the undefined code is treated as retryable, so the call retries for the gax budget before bubbling up. For server-streaming RPCs under additional SDK-level retry (Firestore's QueryUtil._stream), every request hangs for ~45 s and then 500s. We hit a production outage before identifying the underlying chain.
Problem description
CompressionFilter'sIdentityHandler.writeMessage(packages/grpc-js/src/compression-filter.ts) callsmessage.copy(output, 5), which is aBuffer-only method:Uint8Array.prototype.copyis undefined. If the caller-provided request serializer produces aUint8Arrayrather than aBuffer, this throwsTypeError: message.copy is not a function. The thrown error then propagates as acode: undefined,details: undefined, empty-MetadatagRPC status (the empty-status side is tracked separately: #3051).Uint8Arrayrequest serializers are reachable in practice viaprotobufjs, which falls back toUint8Array-returningWriter.allocwheneverprotobufjs.util.Bufferis unset. One concrete trigger is the@protobufjs/inquire@1.1.1regression — see protobufjs/protobuf.js#2214 — where bundlers (Turbopack, certain webpack configurations) statically rewrite the new dynamicrequire(name)to throwMODULE_NOT_FOUND, leavingutil.Buffer = nullin any bundled server build.Reproduction steps
In a Next.js 16 application with
output: "standalone"(Turbopack):@google-cloud/firestore@8.3.0and use it from a Server Component (any call works:collection(...).get(),doc(...).get(), etc.).@protobufjs/inquireresolves to1.1.1(the default withprotobufjs@7.5.6+).next buildand run the standalone output. Trigger any Firestore call.Expected: the call succeeds or fails with a meaningful status.
Actual: the call's callback receives
Error: undefined undefined: undefinedwith{ code: undefined, details: undefined, metadata: Metadata { internalRepr: Map(0) {}, options: {} } }. Running withGRPC_TRACE=all GRPC_VERBOSITY=DEBUGshowscancelWithStatus code: undefined details: "undefined"firing within 1 ms ofwrite() called with message of length N.Independent of
protobufjs, the same path is reachable by registering a custom method whoserequestSerializereturnsnew Uint8Array([...])and callingclient.makeUnaryRequest(...).Environment
engines.node = "24", npm 11@grpc/grpc-js@1.13.4google-gax@5.0.6,@google-cloud/firestore@8.3.0,@grpc/proto-loader@0.7.15, Next.js 16.2.6 with TurbopackAdditional context
Suggested fix:
output.set(message, 5)in place ofmessage.copy(output, 5).BufferextendsUint8Arrayand both expose.set, so the call works for either input.Impact in our deployment: a Dependabot bump to
protobufjs@7.5.8(which pulls@protobufjs/inquire@1.1.1transitively) caused thisTypeErroron every Firestore call. Undergoogle-gaxretry theundefinedcode is treated as retryable, so the call retries for the gax budget before bubbling up. For server-streaming RPCs under additional SDK-level retry (Firestore'sQueryUtil._stream), every request hangs for ~45 s and then 500s. We hit a production outage before identifying the underlying chain.