grpc/grpc-go · error
external processor returned unexpected status
Error message
external processor returned unexpected status %v for request headers, expected %v
What it means
In processInitialHeaders (ext_proc.go:1720), the CommonResponse.Status of a request headers response must be CommonResponse_CONTINUE. Any other value is rejected because the client filter only supports continuing the request through the dataplane at this phase; aborting must use an immediate_response instead.
Solutions
- Set CommonResponse.Status = CommonResponse_CONTINUE on every request_headers response.
- To short-circuit the request, send an immediate_response instead of mutating the headers response status.
- Audit the server response builder to ensure status is explicitly initialized.
Example fix
// before
&procpb.HeadersResponse{Response: &procpb.CommonResponse{ /* status unset */ }}
// after
&procpb.HeadersResponse{Response: &procpb.CommonResponse{Status: procpb.CommonResponse_CONTINUE}} Defensive patterns
Strategy: validation
Validate before calling
// Server-side: always set CONTINUE on header responses
func headerResponse() *procpb.HeadersResponse {
return &procpb.HeadersResponse{Response: &procpb.CommonResponse{Status: procpb.CommonResponse_CONTINUE}}
} Try / catch
// Client-side: surfaces as codes.Internal.
if st, ok := status.FromError(err); ok && strings.Contains(st.Message(), "unexpected status") {
// server set a non-CONTINUE status; fix the server
} Prevention
- Never set a non-CONTINUE status on header/body responses; use immediate_response to abort.
- Default the CommonResponse.Status to CONTINUE in the server response builder.
When it happens
Trigger: External processor returns a request_headers response whose response.status is set to a value other than CONTINUE (e.g., a numeric default of 0 in some implementations that maps to a non-CONTINUE enum value, or an explicit custom status).
Common situations: Server-side bug defaulting the status field, or a server attempting to short-circuit a request by setting a non-CONTINUE status on the headers response rather than using an immediate_response.
Related errors
- external processor returned an unexpected message type %T…
- 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/1a7d18e01cc5fbe8.
Report an issue: GitHub.
Appendix: source
Thrown at internal/xds/httpfilter/extproc/ext_proc.go:1721
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
}
dataplaneCtx := metadata.NewOutgoingContext(ctx, outgoingMD)
if err = cs.createDataplaneStream(dataplaneCtx, newStream, opts); err != nil {
return false
}
return true
}View on GitHub (pinned to 0c51461d27)