grpc/grpc-go · error

external processor sent response body before sending respons

Error message

external processor sent response body before sending response headers

What it means

Raised when the server sends a response_body before the client has delivered (and the server has acknowledged) response headers. Because responseHeaderMode is modeSend, the filter requires response headers to be exchanged first; a response-body message arriving while responseHeadersReady has not fired is out of order and fails the proc stream.

Source

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

			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 03255a9237)

Solutions

  1. Fix the server to always emit its response_headers response (status CONTINUE) before any response_body, matching the negotiated order.
  2. If the server has nothing to mutate on headers, still return an empty response_headers with status CONTINUE once headers arrive.
  3. Enable failure_mode_allow so the RPC survives the violation by bypassing.

Example fix

// before: server sends body first
func Process(stream) {
  headers := <-stream.Recv() // ignored
  stream.Send(&ProcessingResponse{ResponseBody: ...}) // headers ack skipped -> error
}

// after: ack headers, then body
func Process(stream) {
  <-stream.Recv() // response headers
  stream.Send(&ProcessingResponse{ResponseHeaders:{Response:{Status:CONTINUE}}})
  body := <-stream.Recv()
  stream.Send(&ProcessingResponse{ResponseBody: ...})
}
Defensive patterns

Strategy: validation

Validate before calling

// Server: track whether response headers were acknowledged before sending body.
headersAcked := false
// only set headersAcked=true after sending response_headers; gate body sends on it.

Try / catch

// Client: failure_mode_allow -> bypass.

Prevention

When it happens

Trigger: processing_mode.response_header_mode == SEND and the ext-proc server emits a response_body message before returning its response_headers response, i.e. it skipped/collided the header step.

Common situations: Server bug that sends body mutation eagerly without waiting to receive response headers, or a server that only intends to mutate bodies but the client configured response_header_mode SEND expecting an explicit header ack first.

Related errors


AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07). Data as JSON: /api/errors/40146c288cecd680. Report an issue: GitHub.