grpc/grpc-go · error
external processor sent response body before sending…
Error message
external processor sent response body before sending response headers
What it means
Raised by recvFromProcServerLoop (ext_proc.go:1446) when responseHeaderMode is SEND and the server sends a response_body response before the client has forwarded response headers to it (responseHeadersReady has not fired). Response body must follow response headers in the negotiated ordering; failProcStream fails the RPC unless failure_mode_allow bypasses it.
Solutions
- On the server, strictly order responses: only send response_body after you have received the response_headers request from the client.
- If you do not need response header processing, set response_header_mode to SKIP (then this ordering guard is not applied).
- Enable failure_mode_allow so the client tolerates the ordering violation and bypasses ext_proc.
- Add server-side logging of received ProcessingRequest types to confirm the request/response ordering.
Example fix
// before: server sends response body without waiting for response headers
func handle(stream) {
stream.Send(&procpb.ProcessingResponse{Response: &procpb.ProcessingResponse_ResponseBody{...}})
}
// after: wait for the response headers request first
for {
req, _ := stream.Recv()
switch req.Request.(type) {
case *procpb.ProcessingRequest_ResponseHeaders:
stream.Send(&procpb.ProcessingResponse{Response: &procpb.ProcessingResponse_ResponseHeaders{...}})
case *procpb.ProcessingRequest_ResponseBody:
stream.Send(&procpb.ProcessingResponse{Response: &procpb.ProcessingResponse_ResponseBody{...}})
}
} Defensive patterns
Strategy: fallback
Validate before calling
// On the ext_proc SERVER: enforce strict request/response ordering per stream.
type phase int
const (
phaseRespHeaders phase = iota
phaseRespBody
phaseRespTrailers
)
// Only allow sending response_body after phase >= phaseRespBody is reached
// (i.e. after a response_headers request was received and answered). Try / catch
filter.failure_mode_allow = true
if st, ok := status.FromError(err); ok && st.Code() == codes.Internal &&
strings.Contains(st.Message(), "response body before sending response headers") {
// server sent response_body before the client forwarded response headers
} Prevention
- Server: only send response_body after receiving a response_headers ProcessingRequest.
- Set response_header_mode to SKIP if you do not need response header processing (disables this guard).
- Enable failure_mode_allow to tolerate ordering slips.
- Log ProcessingRequest types server-side to verify the order.
When it happens
Trigger: Triggered when response_header_mode == SEND and the server emits response_body (ext_proc.go:1436) before the client has sent the response_headers event the server must reply to first — e.g. the server sends a response body mutation unprompted on a fresh stream.
Common situations: Server handler that fires response body mutations on a timer or out of band without waiting for the response_headers request, or a server that conflates request and response phases and starts emitting response_body immediately.
Related errors
- external processor sent response body after response…
- external processor sent response headers before response…
- external processor returned unexpected status
- external processor sent response trailers before response…
- external processor unexpectedly sent duplicate response…
AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11).
Data as JSON: /api/errors/40146c288cecd680.
Report an issue: GitHub.
Appendix: source
Thrown at internal/xds/httpfilter/extproc/ext_proc.go:1446
if !ok {
return
}
if streamedResp.GetEndOfStream() {
cs.discardRequests.Store(true)
}
cs.mutatedReqBuffer.Put(streamedResp)
case resp.GetResponseBody() != nil:
if cs.config.processingModes.responseBodyMode == modeSkip {
cs.failProcStream(fmt.Errorf("external processor unexpectedly sent response body when response body processing is disabled"))
return
}
// If response headers have been sent and mutated response headers have
// not been received before receiving the response body message, fail the
// RPC.
if cs.config.processingModes.responseHeaderMode == modeSend && !cs.responseHeadersReady.HasFired() {
cs.failProcStream(fmt.Errorf("external processor sent response body before sending response headers"))
return
}
// If mutated response trailers have been received before receiving the
// response body message, fail the RPC.
if cs.config.processingModes.responseTrailerMode == modeSend && cs.responseTrailerReady.HasFired() {
cs.failProcStream(fmt.Errorf("external processor sent response body after response trailers were already processed"))
return
}
streamedResp, ok := cs.validateBodyResponse(resp.GetResponseBody())
if !ok {
return
}
if streamedResp.GetEndOfStream() {
cs.failProcStream(fmt.Errorf("external processor unexpectedly set end of stream in response body mutation"))
return
}View on GitHub (pinned to 0c51461d27)