grpc/grpc-go · error
external processor unexpectedly sent duplicate response…
Error message
external processor unexpectedly sent duplicate response trailers after response trailers were already processed
What it means
Raised by recvFromProcServerLoop (ext_proc.go:1507) when the ext_proc server sends a second response_trailers response after trailers were already processed (responseTrailerReady already fired). Duplicate trailer mutations are not allowed; failProcStream fails the RPC unless failure_mode_allow bypasses it.
Solutions
- On the server, send response_trailers at most once per stream.
- Track per-stream state so the trailer path is not re-entered.
- Enable failure_mode_allow so a duplicate does not fail the user RPC.
- Add server-side unit tests asserting response_trailers is sent exactly once.
Example fix
// before: trailer mutation sent more than once
for { req, _ := stream.Recv(); stream.Send(respTrailers(req)) }
// after: send once, guarded by per-stream state
trailersSent := false
for {
req, _ := stream.Recv()
if _, ok := req.Request.(*procpb.ProcessingRequest_ResponseTrailers); ok && !trailersSent {
stream.Send(respTrailers(req)); trailersSent = true
}
} Defensive patterns
Strategy: fallback
Validate before calling
// On the ext_proc SERVER: ensure response_trailers is sent at most once.
type streamState struct{ respTrailersSent bool }
func (s *streamState) allowRespTrailers() bool {
if s.respTrailersSent { return false }
s.respTrailersSent = true
return true
} Try / catch
filter.failure_mode_allow = true
if st, ok := status.FromError(err); ok && st.Code() == codes.Internal &&
strings.Contains(st.Message(), "duplicate response trailers") {
// server sent response_trailers more than once
} Prevention
- Server: send response_trailers exactly once per stream.
- Guard the response-trailer code path with per-stream state.
- Enable failure_mode_allow so duplicates degrade rather than fail.
- Server unit test: assert response_trailers count == 1 across a full stream.
When it happens
Trigger: Triggered when the server sends response_trailers (ext_proc.go:1497) a second time on a stream where responseTrailerReady.HasFired() is already true.
Common situations: Server handler that re-enters its trailer code path, an unguarded loop sending trailer mutations more than once, or a buggy fan-out handler duplicating responses near stream end.
Related errors
- external processor sent response body after response…
- external processor sent response trailers before response…
- external processor unexpectedly sent duplicate response…
- external processor unexpectedly sent response trailers when…
- external processor returned invalid body mutation in body…
AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11).
Data as JSON: /api/errors/8b048cfede83b07c.
Report an issue: GitHub.
Appendix: source
Thrown at internal/xds/httpfilter/extproc/ext_proc.go:1507
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()
}
}
}
func (cs *clientStream) validateBodyResponse(bodyResp *v3procservicepb.BodyResponse) (*v3procservicepb.StreamedBodyResponse, bool) {
if status := bodyResp.GetResponse().GetStatus(); status != v3procservicepb.CommonResponse_CONTINUE {
cs.failProcStream(fmt.Errorf("external processor returned unexpected status %v for body response, expected %v", status, v3procservicepb.CommonResponse_CONTINUE))
return nil, falseView on GitHub (pinned to 0c51461d27)