{"record":{"id":"a2b9debf446d9c89","repo":"grpc/grpc-go","slug":"extproc-response-message-does-not-implement-proto","errorCode":null,"errorMessage":"extproc: response message does not implement proto.Message","messagePattern":"extproc: response message does not implement proto\\.Message","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/xds/httpfilter/extproc/ext_proc.go","lineNumber":1117,"sourceCode":"func (cs *clientStream) RecvMsg(m any) error {\n\t// Initiate response header processing because external processor requires the\n\t// events to be sent in the correct order, i.e. response header before\n\t// response message. And if Header() has not already been called, send the\n\t// response headers to external processor server first.\n\tif err := cs.initiateResponseHeaderProcessing(); err != nil {\n\t\treturn err\n\t}\n\n\t// If all the responses from external processor server have been drained or if\n\t// the external processor is bypassed, or if the response body mode is skip,\n\t// then receive directly from the dataplane stream.\n\tif cs.responseDrained.Load() || (cs.procStreamBypass.HasFired() && !cs.respForwardingStarted) || cs.config.processingModes.responseBodyMode == modeSkip {\n\t\treturn cs.recvFromDataplane(m)\n\t}\n\n\tmsg, ok := m.(proto.Message)\n\tif !ok {\n\t\treturn fmt.Errorf(\"extproc: response message does not implement proto.Message\")\n\t}\n\n\t// Start the background receiving loop on the first RecvMsg call to capture\n\t// the type of message to be received.\n\tif !cs.respForwardingStarted {\n\t\tcs.respForwardingStarted = true\n\t\tgo cs.responseForwardingToProcServerLoop(msg.ProtoReflect().Type())\n\t}\n\n\t// Pull response messages from mutatedRespBuffer.\n\tselect {\n\tcase streamedResp, ok := <-cs.mutatedRespBuffer.Get():\n\t\tcs.mutatedRespBuffer.Load()\n\t\tif !ok {\n\t\t\t// Closed channel implies that all messages from the external processor\n\t\t\t// have been received. Start receiving directly from dataplane stream.\n\t\t\tcs.responseDrained.Store(true)\n\t\t\treturn cs.recvFromDataplane(m)","sourceCodeStart":1099,"sourceCodeEnd":1135,"githubUrl":"https://github.com/grpc/grpc-go/blob/0c51461d27177d997e14c642fe18c11668fc09a3/internal/xds/httpfilter/extproc/ext_proc.go#L1099-L1135","documentation":"Returned by clientStream.RecvMsg (ext_proc.go:1117) when the destination message m passed to RecvMsg does not implement proto.Message. The ext_proc filter unmarshals response bodies as protobuf, so it requires the caller's receive buffer to be a generated proto message; a plain Go struct or *any is rejected.","triggerScenarios":"Triggered when the application calls RecvMsg on a stream routed through the ext_proc filter with a message that is not a proto.Message — e.g. a manually defined struct, a *[]byte, or a non-proto wrapper — while response body processing mode is GRPC and the message is taken from mutatedRespBuffer.","commonSituations":"Using a hand-rolled message type instead of a .pb.go generated struct, passing &someValue where someValue is not a proto, mixing the ext_proc filter with a non-protobuf streaming service, or generic helpers that accept any losing the proto type.","solutions":["Pass a pointer to a protobuf-generated message type to RecvMsg (the same type the server stream sends).","If you wrote the message by hand, regenerate it from a .proto so it embeds proto.Message/ProtoReflect.","Avoid generic any-typed wrappers around the stream; preserve the concrete proto type at the call site.","Disable response_body_mode (set to NONE) if your RPCs are not protobuf-encoded."],"exampleFix":"// before: non-proto receive buffer\nvar out struct{ Field string }\nstream.RecvMsg(&out) // triggers: does not implement proto.Message\n\n// after: use the generated proto type\nvar out mypb.MyResponse\nstream.RecvMsg(&out)","handlingStrategy":"type-guard","validationCode":"// Validate the receive buffer implements proto.Message before calling RecvMsg.\nfunc ensureProtoMessage(m any) error {\n    if _, ok := m.(proto.Message); !ok {\n        return fmt.Errorf(\"RecvMsg target %T does not implement proto.Message\", m)\n    }\n    return nil\n}","typeGuard":"// Type guard for the RecvMsg destination.\nfunc isProtoMessage(m any) bool {\n    _, ok := m.(proto.Message)\n    return ok\n}","tryCatchPattern":"if err := stream.RecvMsg(out); err != nil {\n    if strings.Contains(err.Error(), \"does not implement proto.Message\") {\n        // programming error: regenerate the message from .proto and pass a *T pointer\n    }\n}","preventionTips":["Always generate messages from .proto and pass &mypb.MyResponse{} to RecvMsg.","Avoid generic stream wrappers that erase the concrete proto type.","Disable response_body_mode (NONE) for non-protobuf streaming services.","Add a compile-time check: var _ proto.Message = (*mypb.MyResponse)(nil)."],"tags":["grpc","xds","extproc","envoy","protobuf","proto-message","programming-error"],"backgroundTag":null,"analyzedSha":"0c51461d27177d997e14c642fe18c11668fc09a3","analyzedAt":"2026-08-11T14:49:15.055Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}