{"record":{"id":"ef6ae1870d026b90","repo":"grpc/grpc-go","slug":"external-processor-returned-an-unexpected-message","errorCode":null,"errorMessage":"external processor returned an unexpected message type %T, expected request headers response","messagePattern":"external processor returned an unexpected message type %T, expected request headers response","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/xds/httpfilter/extproc/ext_proc.go","lineNumber":1543,"sourceCode":"// returns false if the proc stream needs to be bypassed, or failed due to an\n// error, or aborted early by a processor-initiated immediate response.\nfunc (cs *clientStream) processInitialHeaders(ctx context.Context, newStream func(context.Context, ...grpc.CallOption) (grpc.ClientStream, error), opts []grpc.CallOption) bool {\n\tresp, err := cs.procStream.Recv()\n\tif err != nil {\n\t\tcs.handleHeaderError(err, newStream, opts)\n\t\treturn false\n\t}\n\tif resp.GetRequestDrain() {\n\t\tcs.triggerBypass()\n\t}\n\tif resp.GetImmediateResponse() != nil {\n\t\tcs.handleImmediateResponse(resp.GetImmediateResponse(), newStream, opts)\n\t\treturn false\n\t}\n\n\theader := resp.GetRequestHeaders()\n\tif header == nil {\n\t\terr := fmt.Errorf(\"external processor returned an unexpected message type %T, expected request headers response\", resp.GetResponse())\n\t\tcs.handleHeaderError(err, newStream, opts)\n\t\treturn false\n\t}\n\t// Check if status in header response is CONTINUE; if not, fail the stream.\n\tif status := header.GetResponse().GetStatus(); status != v3procservicepb.CommonResponse_CONTINUE {\n\t\tcs.handleHeaderError(fmt.Errorf(\"external processor returned unexpected status %v for request headers, expected %v\", status, v3procservicepb.CommonResponse_CONTINUE), newStream, opts)\n\t\treturn false\n\t}\n\t// Mutate the outgoing headers with additions and removals received from the\n\t// external processor.\n\toutgoingMD, _ := metadata.FromOutgoingContext(ctx)\n\tif outgoingMD == nil {\n\t\toutgoingMD = metadata.MD{}\n\t}\n\tif err = cs.applyMutations(header.GetResponse().GetHeaderMutation(), outgoingMD); err != nil {\n\t\tcs.handleHeaderError(err, newStream, opts)\n\t\treturn false\n\t}","sourceCodeStart":1525,"sourceCodeEnd":1561,"githubUrl":"https://github.com/grpc/grpc-go/blob/03255a9237b6eb32710f6bc4f2de9a675b99fe36/internal/xds/httpfilter/extproc/ext_proc.go#L1525-L1561","documentation":"The very first ProcessingResponse the extproc filter expects from the server after sending request headers must be a request_headers response (ext_proc.go:1541). If the response's oneof resolves to anything else (response headers, body, trailers, etc.), the filter cannot proceed and either bypasses (per failure mode) or fails.","triggerScenarios":"The external processor's first message on the stream carries a non-request-headers response variant, e.g. it sent response_headers or a body response before acknowledging request headers.","commonSituations":"Server mis-implements the handshake ordering; server is a generic proxy that responds to the first request_headers_request with the wrong message type; state machine bug where the server replies with the final response type prematurely.","solutions":["Make the extproc server reply to the initial request_headers request with a ProcessingResponse whose request_headers field is set.","Do not send body/response/trailer messages until the request-headers handshake completes.","Re-test against the envoy.service.ext_proc.v3 contract; ensure the request/response pairing matches the processing modes."],"exampleFix":"// before: server answers request headers with a response_headers message\nstream.Send(&procservicepb.ProcessingResponse{Response: &procservicepb.ProcessingResponse_ResponseHeaders{ResponseHeaders: hv}})\n\n// after: first reply must carry request_headers\nstream.Send(&procservicepb.ProcessingResponse{Response: &procservicepb.ProcessingResponse_RequestHeaders{RequestHeaders: &procservicepb.HeadersResponse{Response: &procservicepb.CommonResponse{Status: procservicepb.CommonResponse_CONTINUE}}}})","handlingStrategy":"validation","validationCode":"// Server side: guarantee the first message is a request_headers response\nif !requestHeadersSent {\n    stream.Send(&procservicepb.ProcessingResponse{Response: &procservicepb.ProcessingResponse_RequestHeaders{RequestHeaders: hdrResp}})\n    requestHeadersSent = true\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Implement the extproc handshake ordering exactly: request_headers first, then body/trailer messages.","Add a server-side state machine test for message ordering.","Set failure_mode_allow to bypass cleanly while debugging."],"tags":["extproc","grpc","xds","protocol","go"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}