grpc/grpc-go · error

external processor unexpectedly sent response body when…

Error message

external processor unexpectedly sent response body when response body processing is disabled

What it means

Raised by recvFromProcServerLoop (ext_proc.go:1438) when the ext_proc server sends a response_body response but responseBodyMode is modeSkip (NONE). This is a protocol violation; failProcStream fails the RPC with codes.Internal unless failure_mode_allow bypasses ext_proc.

Solutions

  1. Ensure the server only returns response_body when ProtocolConfiguration.response_body_mode == GRPC.
  2. Set response_body_mode to GRPC in the xDS config if you actually want response body mutation.
  3. Enable failure_mode_allow so the dataplane RPC continues without ext_proc on this violation.
  4. Fix any shared handler template that unconditionally emits response body mutations.

Example fix

// before: server emits response body even though client mode is NONE
return &procpb.ProcessingResponse{Response: &procpb.ProcessingResponse_ResponseBody{...}}, nil

// after: gate response body output on the negotiated mode
if protocolCfg.GetResponseBodyMode() == procpb.BodySendMode_GRPC {
  return &procpb.ProcessingResponse{Response: &procpb.ProcessingResponse_ResponseBody{...}}, nil
}
Defensive patterns

Strategy: fallback

Validate before calling

// On the ext_proc SERVER: only emit response_body when the client negotiated GRPC.
func shouldEmitResponseBody(protocolCfg *procpb.ProtocolConfiguration) bool {
    return protocolCfg.GetResponseBodyMode() == procpb.BodySendMode_GRPC
}

Try / catch

filter.failure_mode_allow = true
if st, ok := status.FromError(err); ok && st.Code() == codes.Internal &&
    strings.Contains(st.Message(), "unexpectedly sent response body") {
    // server violated the negotiated response body mode
}

Prevention

When it happens

Trigger: Triggered when processing_mode.response_body_mode is NONE/SKIP but the server returns a ProcessingResponse with response_body set (ext_proc.go:1436).

Common situations: Server assumes response body processing is on while xDS configured NONE, a generic handler always mutating response bodies, or version drift between server expectations and the advertised ProtocolConfiguration.

Related errors


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

Appendix: source

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

		switch {
		case resp.GetRequestBody() != nil:
			if cs.config.processingModes.requestBodyMode == modeSkip {
				cs.failProcStream(fmt.Errorf("external processor unexpectedly sent request body when request body processing is disabled"))
				return
			}

			streamedResp, ok := cs.validateBodyResponse(resp.GetRequestBody())
			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
			}

View on GitHub (pinned to 0c51461d27)