{"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":1280,"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":1262,"sourceCodeEnd":1298,"githubUrl":"https://github.com/grpc/grpc-go/blob/03255a9237b6eb32710f6bc4f2de9a675b99fe36/internal/xds/httpfilter/extproc/ext_proc.go#L1262-L1298","documentation":"Raised when the server sends a response_body before the client has delivered (and the server has acknowledged) response headers. Because responseHeaderMode is modeSend, the filter requires response headers to be exchanged first; a response-body message arriving while responseHeadersReady has not fired is out of order and fails the proc stream.","triggerScenarios":"processing_mode.response_header_mode == SEND and the ext-proc server emits a response_body message before returning its response_headers response, i.e. it skipped/collided the header step.","commonSituations":"Server bug that sends body mutation eagerly without waiting to receive response headers, or a server that only intends to mutate bodies but the client configured response_header_mode SEND expecting an explicit header ack first.","solutions":["Fix the server to always emit its response_headers response (status CONTINUE) before any response_body, matching the negotiated order.","If the server has nothing to mutate on headers, still return an empty response_headers with status CONTINUE once headers arrive.","Enable failure_mode_allow so the RPC survives the violation by bypassing."],"exampleFix":"// before: server sends body first\nfunc Process(stream) {\n  headers := <-stream.Recv() // ignored\n  stream.Send(&ProcessingResponse{ResponseBody: ...}) // headers ack skipped -> error\n}\n\n// after: ack headers, then body\nfunc Process(stream) {\n  <-stream.Recv() // response headers\n  stream.Send(&ProcessingResponse{ResponseHeaders:{Response:{Status:CONTINUE}}})\n  body := <-stream.Recv()\n  stream.Send(&ProcessingResponse{ResponseBody: ...})\n}","handlingStrategy":"validation","validationCode":"// Server: track whether response headers were acknowledged before sending body.\nheadersAcked := false\n// only set headersAcked=true after sending response_headers; gate body sends on it.","typeGuard":null,"tryCatchPattern":"// Client: failure_mode_allow -> bypass.","preventionTips":["Server must send response_headers (CONTINUE) before any response_body.","Serialize ProcessingResponse sends to avoid reordering."],"tags":["extproc","protocol-violation","ordering","response-headers","server-bug"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}