grpc/grpc-go · error

external processor sent response body after response…

Error message

external processor sent response body after response trailers were already processed

What it means

Raised by recvFromProcServerLoop (ext_proc.go:1453) when responseTrailerMode is SEND and the server sends response_body after the response trailers have already been processed (responseTrailerReady has fired). Once trailers are done, response body is out of order; failProcStream fails the RPC unless failure_mode_allow bypasses it.

Solutions

  1. On the server, stop sending response_body once you have emitted the response trailers for the stream.
  2. Ensure the server processes events in order (headers -> body -> trailers) and does not interleave them.
  3. Enable failure_mode_allow so the dataplane RPC survives the violation.
  4. Add a per-stream phase machine in the server handler so body mutations cannot be sent post-trailers.

Example fix

// before: server flushes a stray response body mutation after trailers
stream.Send(respTrailers)
stream.Send(respBody) // late, triggers the error

// after: no response body after trailers
stream.Send(respBody)
stream.Send(respTrailers)
Defensive patterns

Strategy: fallback

Validate before calling

// On the ext_proc SERVER: track trailer completion and forbid later body sends.
type streamState struct{ trailersSent bool }
func (s *streamState) canSendResponseBody() bool { return !s.trailersSent }

Try / catch

filter.failure_mode_allow = true
if st, ok := status.FromError(err); ok && st.Code() == codes.Internal &&
    strings.Contains(st.Message(), "response body after response trailers") {
    // server emitted response body after trailers were already processed
}

Prevention

When it happens

Trigger: Triggered when response_trailer_mode == SEND, the client has already forwarded and received mutations for response trailers (responseTrailerReady fired), and the server then sends another response_body (ext_proc.go:1436).

Common situations: Server handler that does not track its own phase and emits late response body mutations after trailers, or a buggy server sending buffered mutations out of order near stream end.

Related errors


AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11). Data as JSON: /api/errors/0f0b56d1dd2ee633. Report an issue: GitHub.

Appendix: source

Thrown at internal/xds/httpfilter/extproc/ext_proc.go:1453

		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 0c51461d27)