{"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":1287,"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":1269,"sourceCodeEnd":1305,"githubUrl":"https://github.com/grpc/grpc-go/blob/03255a9237b6eb32710f6bc4f2de9a675b99fe36/internal/xds/httpfilter/extproc/ext_proc.go#L1269-L1305","documentation":"Raised when a response_body message arrives after response trailers have already been processed (responseTrailerMode == SEND and responseTrailerReady has fired). The required ordering is headers -> body -> trailers; a body after trailers is illegal and fails the proc stream.","triggerScenarios":"The server sends response_body after it has already sent response_trailers, or interleaves them out of order while response_trailer_mode is SEND.","commonSituations":"Server implementation that buffers and flushes trailers before residual body chunks. Concurrency bug in the server emitting trailer and body responses from separate goroutines.","solutions":["Serialize all ProcessingResponse sends in the server and ensure body messages precede the single response_trailers message.","Make sure trailers are only sent once the server has received the body EndOfStream.","Enable failure_mode_allow to bypass while remediating."],"exampleFix":"// before: server sends trailers, then more body\nstream.Send(&ProcessingResponse{ResponseTrailers: ...})\nstream.Send(&ProcessingResponse{ResponseBody: ...}) // -> error\n\n// after: body fully drained before trailers\nfor body := range bodyCh { stream.Send(&ProcessingResponse{ResponseBody: body}) }\nstream.Send(&ProcessingResponse{ResponseTrailers: ...})","handlingStrategy":"validation","validationCode":"// Server: drain all body messages before sending response_trailers.\nfunc sendInOrder(stream pb.ExternalProcessor_ProcessServer) error {\n    // for body in bodies: stream.Send(responseBody(body))\n    // then: stream.Send(responseTrailers(...))\n    return nil\n}","typeGuard":null,"tryCatchPattern":"// Client: failure_mode_allow -> bypass.","preventionTips":["Never emit response_trailers before the final response_body.","Use a single goroutine for all sends to enforce ordering."],"tags":["extproc","protocol-violation","ordering","response-trailers","server-bug"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}