grpc/grpc-go · error
external processor sent response trailers before response…
Error message
external processor sent response trailers before response trailers were sent to it
What it means
Raised by recvFromProcServerLoop (ext_proc.go:1503) when the ext_proc server sends a response_trailers response before the client has forwarded the response trailers (trailerSent is false). The server cannot mutate trailers it has not received; failProcStream fails the RPC unless failure_mode_allow bypasses it.
Solutions
- On the server, only send response_trailers mutations in reply to a response_trailers ProcessingRequest.
- Set failure_mode_allow so the premature mutation does not fail the RPC.
- Add server-side request-type logging to confirm response_trailers is only emitted after one is received.
- If injecting static trailers, do it inside the response_trailers handler, not eagerly.
Example fix
// before: server pushes trailer mutation immediately
func handle(stream) {
stream.Send(&procpb.ProcessingResponse{Response: &procpb.ProcessingResponse_ResponseTrailers{...}})
}
// after: respond only to the matching request type
for {
req, _ := stream.Recv()
if _, ok := req.Request.(*procpb.ProcessingRequest_ResponseTrailers); ok {
stream.Send(&procpb.ProcessingResponse{Response: &procpb.ProcessingResponse_ResponseTrailers{...}})
}
} Defensive patterns
Strategy: fallback
Validate before calling
// On the ext_proc SERVER: only emit response_trailers in reply to a
// response_trailers request.
func shouldAnswerResponseTrailers(req *procpb.ProcessingRequest) bool {
_, ok := req.GetRequest().(*procpb.ProcessingRequest_ResponseTrailers)
return ok
} Try / catch
filter.failure_mode_allow = true
if st, ok := status.FromError(err); ok && st.Code() == codes.Internal &&
strings.Contains(st.Message(), "response trailers before response trailers were sent to it") {
// server pushed response_trailers before the client forwarded them
} Prevention
- Server: send response_trailers only in reply to a response_trailers ProcessingRequest.
- Add static trailer mutations inside the response_trailers handler, not eagerly.
- 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_trailers mutation (ext_proc.go:1497) on a stream where the client has not yet sent the response_trailers event — e.g. the server proactively pushes trailer mutations at stream open.
Common situations: Server that pushes a fixed set of trailers immediately instead of responding to the response_trailers request, or a handler that conflates headers and trailers phases.
Related errors
- external processor sent response body after response…
- external processor sent response body before sending…
- external processor sent response headers before response…
- external processor unexpectedly sent duplicate response…
- external processor unexpectedly sent response trailers when…
AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11).
Data as JSON: /api/errors/16d52958afc35463.
Report an issue: GitHub.
Appendix: source
Thrown at internal/xds/httpfilter/extproc/ext_proc.go:1503
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 0c51461d27)