grpc/grpc-go · error
external processor returned an unexpected message type %T…
Error message
external processor returned an unexpected message type %T, expected request headers response
What it means
After the first Recv() on the extproc stream, processInitialHeaders (ext_proc.go:1714) expects a ProcessingResponse carrying a request_headers response, an immediate_response, or a request_drain. If none of those is set (GetRequestHeaders() == nil), the server returned an out-of-protocol message variant (e.g., response_headers, body_response, or response_trailers) as its first reply.
Solutions
- Fix the external processor so the first response on the stream is a ProcessingResponse with the request_headers variant set (or an immediate_response / request_drain).
- Verify the server branches on the incoming ProcessingRequest oneof and replies with the matching response variant.
- Update the external processor proto/code to the same version grpc-go uses.
Example fix
// before (server)
return &procpb.ProcessingResponse{Response: &procpb.ProcessingResponse_ResponseHeaders{...}}
// after
return &procpb.ProcessingResponse{Response: &procpb.ProcessingResponse_RequestHeaders{RequestHeaders: &procpb.HeadersResponse{Response: &procpb.CommonResponse{Status: procpb.CommonResponse_CONTINUE}}}} Defensive patterns
Strategy: validation
Validate before calling
// Server-side: enforce that the first response is a request_headers response
func firstResponse(req *procpb.ProcessingRequest) *procpb.ProcessingResponse {
switch req.Request.(type) {
case *procpb.ProcessingRequest_RequestHeaders:
return &procpb.ProcessingResponse{Response: &procpb.ProcessingResponse_RequestHeaders{RequestHeaders: &procpb.HeadersResponse{Response: &procpb.CommonResponse{Status: procpb.CommonResponse_CONTINUE}}}}
}
return nil // never lead with another variant
} Try / catch
// Client-side: the stream is failed/bypassed; the RPC surfaces codes.Internal.
if err := stream.SendMsg(...); err != nil {
if st, ok := status.FromError(err); ok && strings.Contains(st.Message(), "unexpected message type") {
// external processor is non-conformant; fix the server
}
} Prevention
- Branch the server response on the incoming ProcessingRequest oneof and reply with the matching response variant.
- Add a conformance test that asserts the first response on a fresh stream is request_headers.
- Pin the external processor proto version to match grpc-go.
When it happens
Trigger: External processor sends a ProcessingResponse whose oneof is set to response_headers, response_body, or response_trailers before ever sending a request_headers response as the first message of the stream.
Common situations: Misimplemented external processor that dispatches responses out of phase, or a server that mirrors request types instead of matching the request phase. Server built against an older or custom proto layout.
Related errors
- external processor returned unexpected status
- external processor returned compressed grpc message which…
- external processor sent an immediate response but immediate…
- external processor returned invalid body mutation in body…
- external processor returned unexpected status
AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11).
Data as JSON: /api/errors/ef6ae1870d026b90.
Report an issue: GitHub.
Appendix: source
Thrown at internal/xds/httpfilter/extproc/ext_proc.go:1715
// returns false if the proc stream needs to be bypassed, or failed due to an
// error, or aborted early by a processor-initiated immediate response.
func (cs *clientStream) processInitialHeaders(ctx context.Context, newStream func(context.Context, ...grpc.CallOption) (grpc.ClientStream, error), opts []grpc.CallOption) bool {
resp, err := cs.procStream.Recv()
if err != nil {
cs.handleHeaderError(err, newStream, opts)
return false
}
if resp.GetRequestDrain() {
cs.triggerBypass()
}
if resp.GetImmediateResponse() != nil {
cs.handleImmediateResponse(resp.GetImmediateResponse(), newStream, opts)
return false
}
header := resp.GetRequestHeaders()
if header == nil {
err := fmt.Errorf("external processor returned an unexpected message type %T, expected request headers response", resp.GetResponse())
cs.handleHeaderError(err, newStream, opts)
return false
}
// Check if status in header response is CONTINUE; if not, fail the stream.
if status := header.GetResponse().GetStatus(); status != v3procservicepb.CommonResponse_CONTINUE {
cs.handleHeaderError(fmt.Errorf("external processor returned unexpected status %v for request headers, expected %v", status, v3procservicepb.CommonResponse_CONTINUE), newStream, opts)
return false
}
// Mutate the outgoing headers with additions and removals received from the
// external processor.
outgoingMD, _ := metadata.FromOutgoingContext(ctx)
if outgoingMD == nil {
outgoingMD = metadata.MD{}
}
if err = cs.applyMutations(header.GetResponse().GetHeaderMutation(), outgoingMD); err != nil {
cs.handleHeaderError(err, newStream, opts)
return false
}View on GitHub (pinned to 0c51461d27)