grpc/grpc-go · error
external processor sent response headers before response…
Error message
external processor sent response headers before response headers were sent to it
What it means
Raised by recvFromProcServerLoop (ext_proc.go:1473) when the ext_proc server sends a response_headers response before the client has sent it the response headers (responseHeaderSent is false). The server cannot mutate headers it has not received; failProcStream fails the RPC unless failure_mode_allow bypasses it.
Solutions
- On the server, only send response_headers mutations in reply to a response_headers ProcessingRequest.
- Set failure_mode_allow so the dataplane RPC is not failed by the premature mutation.
- Add server-side request-type logging to confirm you only emit response_headers after receiving one.
- If you only need to add a static header, do it inside the response_headers handler, not eagerly.
Example fix
// before: server pushes a response header mutation immediately
func handle(stream) {
stream.Send(&procpb.ProcessingResponse{Response: &procpb.ProcessingResponse_ResponseHeaders{...}})
}
// after: respond only to the matching request type
for {
req, _ := stream.Recv()
if _, ok := req.Request.(*procpb.ProcessingRequest_ResponseHeaders); ok {
stream.Send(&procpb.ProcessingResponse{Response: &procpb.ProcessingResponse_ResponseHeaders{...}})
}
} Defensive patterns
Strategy: fallback
Validate before calling
// On the ext_proc SERVER: only emit response_headers in reply to a
// response_headers request, never eagerly.
func shouldAnswerResponseHeaders(req *procpb.ProcessingRequest, sent bool) bool {
_, isRespHdrs := req.GetRequest().(*procpb.ProcessingRequest_ResponseHeaders)
return isRespHdrs && sent
} Try / catch
filter.failure_mode_allow = true
if st, ok := status.FromError(err); ok && st.Code() == codes.Internal &&
strings.Contains(st.Message(), "response headers before response headers were sent to it") {
// server pushed response_headers before the client forwarded them
} Prevention
- Server: send response_headers only in reply to a response_headers ProcessingRequest.
- Add static header mutations inside the response_headers handler, not at stream open.
- Enable failure_mode_allow so a premature mutation does not fail the RPC.
- Log received ProcessingRequest types server-side to confirm ordering.
When it happens
Trigger: Triggered when the server emits a response_headers mutation (ext_proc.go:1467) on a stream where the client has not yet forwarded the response_headers event — e.g. the server proactively pushes a header mutation on stream open.
Common situations: Server that injects a fixed response header set at stream start rather than in response to the client's response_headers request, or a handler that confuses request_headers and response_headers phases.
Related errors
- external processor sent response body before sending…
- external processor returned unexpected status
- external processor sent response body after response…
- external processor sent response trailers before response…
- external processor unexpectedly sent duplicate response…
AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11).
Data as JSON: /api/errors/4273cb6fc2c8cc26.
Report an issue: GitHub.
Appendix: source
Thrown at internal/xds/httpfilter/extproc/ext_proc.go:1473
}
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 0c51461d27)