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 when the server sends response_trailers while responseTrailerMode is modeSkip. The client did not negotiate response-trailer processing, so a response-trailers message is a protocol violation and the proc stream is failed.

Source

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

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

Solutions

  1. Set processing_mode.response_trailer_mode to SEND if trailer processing is desired.
  2. Otherwise fix the server to not emit response_trailers when trailers are not negotiated.
  3. Enable failure_mode_allow to bypass during remediation.

Example fix

// before
processing_mode:
  response_trailer_mode: SKIP   # server sends response_trailers anyway

// after
processing_mode:
  response_trailer_mode: SEND
Defensive patterns

Strategy: validation

Validate before calling

// Server: suppress response_trailers when negotiated trailer mode is SKIP.
func maybeResponseTrailers(mode v3procfilterpb.ProcessingMode_HeaderSendMode, resp *pb.ProcessingResponse) *pb.ProcessingResponse {
    if mode == v3procfilterpb.ProcessingMode_SKIP && resp.GetResponseTrailers() != nil {
        return nil
    }
    return resp
}

Try / catch

// Client: failure_mode_allow -> bypass.

Prevention

When it happens

Trigger: processing_mode.response_trailer_mode == SKIP and the server emits a ProcessingResponse.response_trailers.

Common situations: Server always mutates trailers; client left response_trailer_mode at SKIP (the default when not using GRPC body mode). Per-route override disabled trailers but shared server still emits them.

Related errors


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