{"record":{"id":"40146c288cecd680","repo":"grpc/grpc-go","slug":"external-processor-sent-response-body-before-sendi","errorCode":null,"errorMessage":"external processor sent response body before sending response headers","messagePattern":"external processor sent response body before sending response headers","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/xds/httpfilter/extproc/ext_proc.go","lineNumber":1446,"sourceCode":"\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif streamedResp.GetEndOfStream() {\n\t\t\t\tcs.discardRequests.Store(true)\n\t\t\t}\n\t\t\tcs.mutatedReqBuffer.Put(streamedResp)\n\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}","sourceCodeStart":1428,"sourceCodeEnd":1464,"githubUrl":"https://github.com/grpc/grpc-go/blob/0c51461d27177d997e14c642fe18c11668fc09a3/internal/xds/httpfilter/extproc/ext_proc.go#L1428-L1464","documentation":"Raised by recvFromProcServerLoop (ext_proc.go:1446) when responseHeaderMode is SEND and the server sends a response_body response before the client has forwarded response headers to it (responseHeadersReady has not fired). Response body must follow response headers in the negotiated ordering; failProcStream fails the RPC unless failure_mode_allow bypasses it.","triggerScenarios":"Triggered when response_header_mode == SEND and the server emits response_body (ext_proc.go:1436) before the client has sent the response_headers event the server must reply to first — e.g. the server sends a response body mutation unprompted on a fresh stream.","commonSituations":"Server handler that fires response body mutations on a timer or out of band without waiting for the response_headers request, or a server that conflates request and response phases and starts emitting response_body immediately.","solutions":["On the server, strictly order responses: only send response_body after you have received the response_headers request from the client.","If you do not need response header processing, set response_header_mode to SKIP (then this ordering guard is not applied).","Enable failure_mode_allow so the client tolerates the ordering violation and bypasses ext_proc.","Add server-side logging of received ProcessingRequest types to confirm the request/response ordering."],"exampleFix":"// before: server sends response body without waiting for response headers\nfunc handle(stream) {\n  stream.Send(&procpb.ProcessingResponse{Response: &procpb.ProcessingResponse_ResponseBody{...}})\n}\n\n// after: wait for the response headers request first\nfor {\n  req, _ := stream.Recv()\n  switch req.Request.(type) {\n  case *procpb.ProcessingRequest_ResponseHeaders:\n    stream.Send(&procpb.ProcessingResponse{Response: &procpb.ProcessingResponse_ResponseHeaders{...}})\n  case *procpb.ProcessingRequest_ResponseBody:\n    stream.Send(&procpb.ProcessingResponse{Response: &procpb.ProcessingResponse_ResponseBody{...}})\n  }\n}","handlingStrategy":"fallback","validationCode":"// On the ext_proc SERVER: enforce strict request/response ordering per stream.\ntype phase int\nconst (\n    phaseRespHeaders phase = iota\n    phaseRespBody\n    phaseRespTrailers\n)\n// Only allow sending response_body after phase >= phaseRespBody is reached\n// (i.e. after a response_headers request was received and answered).","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 before sending response headers\") {\n    // server sent response_body before the client forwarded response headers\n}","preventionTips":["Server: only send response_body after receiving a response_headers ProcessingRequest.","Set response_header_mode to SKIP if you do not need response header processing (disables this guard).","Enable failure_mode_allow to tolerate ordering slips.","Log ProcessingRequest types server-side to verify the order."],"tags":["grpc","xds","extproc","envoy","protocol-violation","ordering","response-body","response-headers"],"backgroundTag":null,"analyzedSha":"0c51461d27177d997e14c642fe18c11668fc09a3","analyzedAt":"2026-08-11T14:49:15.055Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}