grpc/grpc-go · error
external processor returned unexpected status
Error message
external processor returned unexpected status %v for body response, expected %v
What it means
Raised by validateBodyResponse (ext_proc.go:1524) when a request_body or response_body response from the server has a status other than CONTINUE. Body responses must use CommonResponse.CONTINUE; any other status is a protocol violation. failProcStream fails the RPC unless failure_mode_allow bypasses it.
Solutions
- On the server, always set status = CommonResponse.CONTINUE on body responses.
- If the server needs to abort the RPC, use the immediate_response field with a proper gRPC status instead.
- Enable failure_mode_allow so the client tolerates the bad status and bypasses ext_proc.
- Separate the header and body response construction so status logic is not copied across paths.
Example fix
// before: body response carries a non-CONTINUE status
&procpb.BodyResponse{Response: &procpb.CommonResponse{Status: procpb.CommonResponse_RESET, BodyMutation: mut}}
// after: body responses must use CONTINUE
&procpb.BodyResponse{Response: &procpb.CommonResponse{Status: procpb.CommonResponse_CONTINUE, BodyMutation: mut}} Defensive patterns
Strategy: fallback
Validate before calling
// On the ext_proc SERVER: body responses must always carry CONTINUE.
func buildBodyResponse(mut *procpb.BodyMutation) *procpb.BodyResponse {
return &procpb.BodyResponse{Response: &procpb.CommonResponse{
Status: procpb.CommonResponse_CONTINUE,
BodyMutation: mut,
}}
} Try / catch
filter.failure_mode_allow = true
if st, ok := status.FromError(err); ok && st.Code() == codes.Internal &&
strings.Contains(st.Message(), "unexpected status") &&
strings.Contains(st.Message(), "for body response") {
// server returned a non-CONTINUE status on a body response
} Prevention
- Server: always set status = CONTINUE on body responses.
- Use immediate_response to abort the RPC with a proper gRPC status instead.
- Enable failure_mode_allow to tolerate the bad status.
- Keep header-status and body-status construction separate to avoid copy-paste.
When it happens
Trigger: Triggered when bodyResp.GetResponse().GetStatus() (ext_proc.go:1523) != CONTINUE on a body message the client received from the server.
Common situations: Server attempts to reset/replace a body via status on the body response (not supported here), reuses header-status logic on the body path, or returns an error status instead of failing the RPC explicitly.
Related errors
- external processor returned invalid body mutation in body…
- external processor returned unexpected status
- external processor sent response body after response…
- external processor sent response body before sending…
- external processor sent response headers before response…
AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11).
Data as JSON: /api/errors/159c2b73c098cb33.
Report an issue: GitHub.
Appendix: source
Thrown at internal/xds/httpfilter/extproc/ext_proc.go:1524
if cs.responseTrailerReady.HasFired() {
cs.failProcStream(fmt.Errorf("external processor unexpectedly sent duplicate response trailers after response trailers were already processed"))
return
}
trailer := resp.GetResponseTrailers()
if err = cs.applyMutations(trailer.GetHeaderMutation(), cs.responseTrailers); err != nil {
cs.failProcStream(err)
return
}
// Signal that the response trailer is modified and ready to be sent to
// the client.
cs.fireResponseTrailerReady()
}
}
}
func (cs *clientStream) validateBodyResponse(bodyResp *v3procservicepb.BodyResponse) (*v3procservicepb.StreamedBodyResponse, bool) {
if status := bodyResp.GetResponse().GetStatus(); status != v3procservicepb.CommonResponse_CONTINUE {
cs.failProcStream(fmt.Errorf("external processor returned unexpected status %v for body response, expected %v", status, v3procservicepb.CommonResponse_CONTINUE))
return nil, false
}
streamedResp := bodyResp.GetResponse().GetBodyMutation().GetStreamedResponse()
if streamedResp == nil {
cs.failProcStream(fmt.Errorf("external processor returned invalid body mutation in body response"))
return nil, false
}
if streamedResp.GetGrpcMessageCompressed() {
cs.failProcStream(fmt.Errorf("external processor returned compressed grpc message which is not supported"))
return nil, false
}
return streamedResp, true
}
func (cs *clientStream) applyMutations(mutation *v3procservicepb.HeaderMutation, md metadata.MD) error {
if mutation == nil {
return nil
}View on GitHub (pinned to 0c51461d27)