{"record":{"id":"0f0b56d1dd2ee633","repo":"grpc/grpc-go","slug":"external-processor-sent-response-body-after-respon","errorCode":null,"errorMessage":"external processor sent response body after response trailers were already processed","messagePattern":"external processor sent response body after response trailers were already processed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/xds/httpfilter/extproc/ext_proc.go","lineNumber":1453,"sourceCode":"\n\t\tcase resp.GetResponseBody() != nil:\n\t\t\tif cs.config.processingModes.responseBodyMode == modeSkip {\n\t\t\t\tcs.failProcStream(fmt.Errorf(\"external processor unexpectedly sent response body when response body processing is disabled\"))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// If response headers have been sent and mutated response headers have\n\t\t\t// not been received before receiving the response body message, fail the\n\t\t\t// RPC.\n\t\t\tif cs.config.processingModes.responseHeaderMode == modeSend && !cs.responseHeadersReady.HasFired() {\n\t\t\t\tcs.failProcStream(fmt.Errorf(\"external processor sent response body before sending response headers\"))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// If mutated response trailers have been received before receiving the\n\t\t\t// response body message, fail the RPC.\n\t\t\tif cs.config.processingModes.responseTrailerMode == modeSend && cs.responseTrailerReady.HasFired() {\n\t\t\t\tcs.failProcStream(fmt.Errorf(\"external processor sent response body after response trailers were already processed\"))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tstreamedResp, ok := cs.validateBodyResponse(resp.GetResponseBody())\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif streamedResp.GetEndOfStream() {\n\t\t\t\tcs.failProcStream(fmt.Errorf(\"external processor unexpectedly set end of stream in response body mutation\"))\n\t\t\t\treturn\n\t\t\t}\n\t\t\tcs.mutatedRespBuffer.Put(streamedResp)\n\n\t\tcase resp.GetResponseHeaders() != nil:\n\t\t\tif cs.config.processingModes.responseHeaderMode == modeSkip {\n\t\t\t\tcs.failProcStream(fmt.Errorf(\"external processor unexpectedly sent response headers when response header processing is disabled\"))\n\t\t\t\treturn\n\t\t\t}","sourceCodeStart":1435,"sourceCodeEnd":1471,"githubUrl":"https://github.com/grpc/grpc-go/blob/0c51461d27177d997e14c642fe18c11668fc09a3/internal/xds/httpfilter/extproc/ext_proc.go#L1435-L1471","documentation":"Raised by recvFromProcServerLoop (ext_proc.go:1453) when responseTrailerMode is SEND and the server sends response_body after the response trailers have already been processed (responseTrailerReady has fired). Once trailers are done, response body is out of order; failProcStream fails the RPC unless failure_mode_allow bypasses it.","triggerScenarios":"Triggered when response_trailer_mode == SEND, the client has already forwarded and received mutations for response trailers (responseTrailerReady fired), and the server then sends another response_body (ext_proc.go:1436).","commonSituations":"Server handler that does not track its own phase and emits late response body mutations after trailers, or a buggy server sending buffered mutations out of order near stream end.","solutions":["On the server, stop sending response_body once you have emitted the response trailers for the stream.","Ensure the server processes events in order (headers -> body -> trailers) and does not interleave them.","Enable failure_mode_allow so the dataplane RPC survives the violation.","Add a per-stream phase machine in the server handler so body mutations cannot be sent post-trailers."],"exampleFix":"// before: server flushes a stray response body mutation after trailers\nstream.Send(respTrailers)\nstream.Send(respBody) // late, triggers the error\n\n// after: no response body after trailers\nstream.Send(respBody)\nstream.Send(respTrailers)","handlingStrategy":"fallback","validationCode":"// On the ext_proc SERVER: track trailer completion and forbid later body sends.\ntype streamState struct{ trailersSent bool }\nfunc (s *streamState) canSendResponseBody() bool { return !s.trailersSent }","typeGuard":null,"tryCatchPattern":"filter.failure_mode_allow = true\nif st, ok := status.FromError(err); ok && st.Code() == codes.Internal &&\n    strings.Contains(st.Message(), \"response body after response trailers\") {\n    // server emitted response body after trailers were already processed\n}","preventionTips":["Server: never send response_body after you have sent response_trailers for the stream.","Implement a per-stream phase machine (headers -> body -> trailers).","Enable failure_mode_allow so late body mutations degrade gracefully.","Add a server unit test that asserts no body mutation follows trailers."],"tags":["grpc","xds","extproc","envoy","protocol-violation","ordering","response-body","response-trailers"],"backgroundTag":null,"analyzedSha":"0c51461d27177d997e14c642fe18c11668fc09a3","analyzedAt":"2026-08-11T14:49:15.055Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}