grpc/grpc-go · error

external processor unexpectedly sent response trailers when…

Error message

external processor unexpectedly sent response trailers when response trailer processing is disabled

What it means

Raised by recvFromProcServerLoop (ext_proc.go:1499) when responseTrailerMode is modeSkip but the ext_proc server sends a response_trailers response. Mutating response trailers the client never agreed to forward is a protocol violation; failProcStream fails the RPC unless failure_mode_allow bypasses it.

Solutions

  1. On the server, only return response_trailers when the client's ProtocolConfiguration indicates response trailer processing is enabled.
  2. If trailer mutation is desired, set response_trailer_mode to SEND in the xDS config.
  3. Enable failure_mode_allow so the client tolerates the violation and proceeds to the dataplane.
  4. Make the server handler read the negotiated modes and skip trailer output when disabled.

Example fix

// before: server always sends response trailer mutation
return &procpb.ProcessingResponse{Response: &procpb.ProcessingResponse_ResponseTrailers{...}}, nil

// after: only when response trailer mode is enabled on the client
if respTrailerModeEnabled {
  return &procpb.ProcessingResponse{Response: &procpb.ProcessingResponse_ResponseTrailers{...}}, nil
}
Defensive patterns

Strategy: fallback

Validate before calling

// On the ext_proc SERVER: gate response_trailers output on negotiated mode.
func shouldEmitResponseTrailers(responseTrailerModeEnabled bool) bool {
    return responseTrailerModeEnabled
}

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 trailers") {
    // server returned response_trailers while client mode is SKIP
}

Prevention

When it happens

Trigger: Triggered when response_trailer_mode is SKIP and the server returns a ProcessingResponse with response_trailers set (ext_proc.go:1497).

Common situations: Server assumes response trailer processing is on, a shared handler that always emits trailer mutations, or config drift between server expectations and the xDS processing_mode.

Related errors


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

Appendix: source

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

			header := resp.GetResponseHeaders()
			// Check if the status in the header response is CONTINUE; if not, fail
			// the stream.
			if status := header.GetResponse().GetStatus(); status != v3procservicepb.CommonResponse_CONTINUE {
				cs.failProcStream(fmt.Errorf("external processor returned unexpected status %v for response headers, expected %v", status, v3procservicepb.CommonResponse_CONTINUE))
				return
			}
			if err = cs.applyMutations(header.GetResponse().GetHeaderMutation(), cs.responseHeader); err != nil {
				cs.failProcStream(err)
				return
			}
			// Signal that the response header is modified and ready to be sent to the
			// client, so that if there is any buffered response body, it can be sent
			// after the header.
			cs.fireResponseHeadersReady()

		case resp.GetResponseTrailers() != nil:
			if cs.config.processingModes.responseTrailerMode == modeSkip {
				cs.failProcStream(fmt.Errorf("external processor unexpectedly sent response trailers when response trailer processing is disabled"))
				return
			}
			if !cs.trailerSent.Load() {
				cs.failProcStream(fmt.Errorf("external processor sent response trailers before response trailers were sent to it"))
				return
			}
			if cs.responseTrailerReady.HasFired() {
				cs.failProcStream(fmt.Errorf("external processor unexpectedly sent duplicate response trailers after response trailers were already processed"))
				return
			}
			trailer := resp.GetResponseTrailers()
			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()

View on GitHub (pinned to 0c51461d27)