grpc/grpc-go · error
external processor sent response body after response trailer
Error message
external processor sent response body after response trailers were already processed
What it means
Raised when a response_body message arrives after response trailers have already been processed (responseTrailerMode == SEND and responseTrailerReady has fired). The required ordering is headers -> body -> trailers; a body after trailers is illegal and fails the proc stream.
Source
Thrown at internal/xds/httpfilter/extproc/ext_proc.go:1287
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
}
cs.mutatedRespBuffer.Put(streamedResp)
case resp.GetResponseHeaders() != nil:
if cs.config.processingModes.responseHeaderMode == modeSkip {
cs.failProcStream(fmt.Errorf("external processor unexpectedly sent response headers when response header processing is disabled"))
return
}View on GitHub (pinned to 03255a9237)
Solutions
- Serialize all ProcessingResponse sends in the server and ensure body messages precede the single response_trailers message.
- Make sure trailers are only sent once the server has received the body EndOfStream.
- Enable failure_mode_allow to bypass while remediating.
Example fix
// before: server sends trailers, then more body
stream.Send(&ProcessingResponse{ResponseTrailers: ...})
stream.Send(&ProcessingResponse{ResponseBody: ...}) // -> error
// after: body fully drained before trailers
for body := range bodyCh { stream.Send(&ProcessingResponse{ResponseBody: body}) }
stream.Send(&ProcessingResponse{ResponseTrailers: ...}) Defensive patterns
Strategy: validation
Validate before calling
// Server: drain all body messages before sending response_trailers.
func sendInOrder(stream pb.ExternalProcessor_ProcessServer) error {
// for body in bodies: stream.Send(responseBody(body))
// then: stream.Send(responseTrailers(...))
return nil
} Try / catch
// Client: failure_mode_allow -> bypass.
Prevention
- Never emit response_trailers before the final response_body.
- Use a single goroutine for all sends to enforce ordering.
When it happens
Trigger: The server sends response_body after it has already sent response_trailers, or interleaves them out of order while response_trailer_mode is SEND.
Common situations: Server implementation that buffers and flushes trailers before residual body chunks. Concurrency bug in the server emitting trailer and body responses from separate goroutines.
Related errors
- external processor sent response trailers before response tr
- external processor sent response body before sending respons
- external processor sent response headers before response hea
- external processor unexpectedly sent duplicate response head
- external processor unexpectedly sent response trailers when
AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07).
Data as JSON: /api/errors/0f0b56d1dd2ee633.
Report an issue: GitHub.