grpc/grpc-go · error
external processor unexpectedly sent duplicate response head
Error message
external processor unexpectedly sent duplicate response headers after response headers were already processed
What it means
Raised when the server sends response_headers a second time after responseHeadersReady has already fired. Response headers may be mutated exactly once; a duplicate response_headers message is a protocol violation and fails the proc stream.
Source
Thrown at internal/xds/httpfilter/extproc/ext_proc.go:1311
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
}
// 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()View on GitHub (pinned to 03255a9237)
Solutions
- Ensure the server sends exactly one response_headers response per RPC.
- Track in the server whether headers were already acked and skip subsequent sends.
- Enable failure_mode_allow to bypass while remediating.
Example fix
// before: server acks headers on every receive loop
for { req := <-stream.Recv(); stream.Send(responseHeadersFor(req)) }
// after: ack headers once
headersAcked := false
for {
req := <-stream.Recv()
if req.GetResponseHeaders() != nil && !headersAcked {
stream.Send(responseHeadersFor(req)); headersAcked = true
}
} Defensive patterns
Strategy: validation
Validate before calling
// Server: send response_headers exactly once.
respHeadersSent := false
// if req is response_headers && !respHeadersSent { Send(...); respHeadersSent = true } Try / catch
// Client: failure_mode_allow -> bypass.
Prevention
- Track a one-shot flag for response_headers in the server.
- Avoid re-entrant/retry loops that re-send headers.
When it happens
Trigger: The server emits two (or more) response_headers ProcessingResponse messages on the same stream.
Common situations: Server bug where a retry/loop sends response_headers twice. Server designed to 'update' headers later, which the protocol forbids.
Related errors
- external processor sent response body before sending respons
- external processor sent response headers before response hea
- external processor sent response body after response trailer
- external processor unexpectedly sent response headers when r
- external processor returned unexpected status %v for respons
AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07).
Data as JSON: /api/errors/587324ff759bea72.
Report an issue: GitHub.