grpc/grpc-go · error

external processor returned invalid body mutation in body…

Error message

external processor returned invalid body mutation in body response

What it means

Raised by validateBodyResponse (ext_proc.go:1529) when the body mutation in a body response is not the streamed_response variant (GetBodyMutation().GetStreamedResponse() is nil). This ext_proc implementation only supports streamed gRPC body mutations; other body_mutation oneofs are rejected. failProcStream fails the RPC unless failure_mode_allow bypasses it.

Solutions

  1. On the server, wrap body bytes in BodyMutation.StreamedResponse{Body: bytes} rather than BodyMutation.Body.
  2. Enable failure_mode_allow so the client bypasses ext_proc when the server uses an unsupported mutation shape.
  3. Regenerate/validate your server's BodyResponse construction against the gRPC-Go ext_proc subset.
  4. Add a server-side helper buildBodyMutation(bytes) that always returns the streamed_response oneof.

Example fix

// before: server uses the plain body oneof (unsupported here)
BodyMutation: &procpb.BodyMutation{BodyMutation: &procpb.BodyMutation_Body{Body: chunk}}

// after: use the streamed_response oneof
BodyMutation: &procpb.BodyMutation{BodyMutation: &procpb.BodyMutation_StreamedResponse{
  StreamedResponse: &procpb.StreamedBodyResponse{Body: chunk},
}}
Defensive patterns

Strategy: fallback

Validate before calling

// On the ext_proc SERVER: build body mutations using the streamed_response oneof
// supported by the gRPC-Go ext_proc client.
func buildBodyMutation(chunk []byte) *procpb.BodyMutation {
    return &procpb.BodyMutation{BodyMutation: &procpb.BodyMutation_StreamedResponse{
        StreamedResponse: &procpb.StreamedBodyResponse{Body: chunk},
    }}
}

Try / catch

filter.failure_mode_allow = true
if st, ok := status.FromError(err); ok && st.Code() == codes.Internal &&
    strings.Contains(st.Message(), "invalid body mutation in body response") {
    // server used an unsupported body_mutation oneof (e.g. plain body bytes)
}

Prevention

When it happens

Trigger: Triggered when the server's body response sets body_mutation to the raw bytes variant (BodyMutation.Body) instead of BodyMutation.StreamedResponse, or leaves body_mutation unset (ext_proc.go:1527-1528).

Common situations: Server follows the full Envoy ext_proc spec (which allows the plain body oneof) rather than the gRPC-Go subset, a handler that writes body_mutation.body directly, or a copy-paste from an Envoy filter example.

Related errors


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

Appendix: source

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

			if err = cs.applyMutations(trailer.GetHeaderMutation(), cs.responseTrailers); err != nil {
				cs.failProcStream(err)
				return
			}
			// Signal that the response trailer is modified and ready to be sent to
			// the client.
			cs.fireResponseTrailerReady()
		}
	}
}

func (cs *clientStream) validateBodyResponse(bodyResp *v3procservicepb.BodyResponse) (*v3procservicepb.StreamedBodyResponse, bool) {
	if status := bodyResp.GetResponse().GetStatus(); status != v3procservicepb.CommonResponse_CONTINUE {
		cs.failProcStream(fmt.Errorf("external processor returned unexpected status %v for body response, expected %v", status, v3procservicepb.CommonResponse_CONTINUE))
		return nil, false
	}
	streamedResp := bodyResp.GetResponse().GetBodyMutation().GetStreamedResponse()
	if streamedResp == nil {
		cs.failProcStream(fmt.Errorf("external processor returned invalid body mutation in body response"))
		return nil, false
	}
	if streamedResp.GetGrpcMessageCompressed() {
		cs.failProcStream(fmt.Errorf("external processor returned compressed grpc message which is not supported"))
		return nil, false
	}
	return streamedResp, true
}

func (cs *clientStream) applyMutations(mutation *v3procservicepb.HeaderMutation, md metadata.MD) error {
	if mutation == nil {
		return nil
	}
	if err := cs.config.mutationRules.ApplyAdditions(mutation.GetSetHeaders(), md); err != nil {
		return err
	}
	return cs.config.mutationRules.ApplyRemovals(mutation.GetRemoveHeaders(), md)
}

View on GitHub (pinned to 0c51461d27)