grpc/grpc-go · error

external processor sent response headers before response hea

Error message

external processor sent response headers before response headers were sent to it

What it means

Raised when the server sends response_headers before the client has sent response headers to it (responseHeaderSent is false). The ext-proc server must only mutate headers it has first received; responding with header mutations unsolicited is out of order and fails the proc stream.

Source

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

			}

			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
			}
			cs.mutatedRespBuffer.Put(streamedResp)

		case resp.GetResponseHeaders() != nil:
			if cs.config.processingModes.responseHeaderMode == modeSkip {
				cs.failProcStream(fmt.Errorf("external processor unexpectedly sent response headers when response header processing is disabled"))
				return
			}
			if !cs.responseHeaderSent.Load() {
				cs.failProcStream(fmt.Errorf("external processor sent response headers before response headers were sent to it"))
				return
			}
			if cs.responseHeadersReady.HasFired() {
				cs.failProcStream(fmt.Errorf("external processor unexpectedly sent duplicate response headers after response headers were already processed"))
				return
			}

			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
			}

View on GitHub (pinned to 03255a9237)

Solutions

  1. Make the server wait to receive response_headers from the client before sending any response_headers response.
  2. If the intent is request-side injection, do it in a request_headers response instead.
  3. Enable failure_mode_allow to bypass during remediation.

Example fix

// before: server pushes response headers unsolicited
func Process(stream) {
  stream.Send(&pb.ProcessingResponse{ResponseHeaders: inject})
}

// after: wait for client to send response headers first
func Process(stream) {
  for {
    req, _ := stream.Recv()
    if req.GetResponseHeaders() != nil {
      stream.Send(&pb.ProcessingResponse{ResponseHeaders: mutate(req)})
    }
  }
}
Defensive patterns

Strategy: validation

Validate before calling

// Server: only send response_headers after receiving response_headers from client.
seenRespHeaders := false
// for each req from Recv: if req.GetResponseHeaders()!=nil { seenRespHeaders=true }
// only emit response_headers resp when seenRespHeaders

Try / catch

// Client: failure_mode_allow -> bypass.

Prevention

When it happens

Trigger: The server emits a response_headers ProcessingResponse without first receiving a response_headers ProcessingRequest from the client.

Common situations: Server pushes response-header mutations proactively (e.g. to inject headers) instead of waiting for the client's response_headers message. Misuse of a server-side-initiated pattern that the protocol does not allow.

Related errors


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