{"record":{"id":"587324ff759bea72","repo":"grpc/grpc-go","slug":"external-processor-unexpectedly-sent-duplicate-res","errorCode":null,"errorMessage":"external processor unexpectedly sent duplicate response headers after response headers were already processed","messagePattern":"external processor unexpectedly sent duplicate response headers after response headers were already processed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/xds/httpfilter/extproc/ext_proc.go","lineNumber":1311,"sourceCode":"\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}\n\t\t\tif !cs.responseHeaderSent.Load() {\n\t\t\t\tcs.failProcStream(fmt.Errorf(\"external processor sent response headers before response headers were sent to it\"))\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif cs.responseHeadersReady.HasFired() {\n\t\t\t\tcs.failProcStream(fmt.Errorf(\"external processor unexpectedly sent duplicate response headers after response headers were already processed\"))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\theader := resp.GetResponseHeaders()\n\t\t\t// Check if the status in the header response is CONTINUE; if not, fail\n\t\t\t// the stream.\n\t\t\tif status := header.GetResponse().GetStatus(); status != v3procservicepb.CommonResponse_CONTINUE {\n\t\t\t\tcs.failProcStream(fmt.Errorf(\"external processor returned unexpected status %v for response headers, expected %v\", status, v3procservicepb.CommonResponse_CONTINUE))\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err = cs.applyMutations(header.GetResponse().GetHeaderMutation(), cs.responseHeader); err != nil {\n\t\t\t\tcs.failProcStream(err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// Signal that the response header is modified and ready to be sent to the\n\t\t\t// client, so that if there is any buffered response body, it can be sent\n\t\t\t// after the header.\n\t\t\tcs.fireResponseHeadersReady()","sourceCodeStart":1293,"sourceCodeEnd":1329,"githubUrl":"https://github.com/grpc/grpc-go/blob/03255a9237b6eb32710f6bc4f2de9a675b99fe36/internal/xds/httpfilter/extproc/ext_proc.go#L1293-L1329","documentation":"Raised when the server sends response_headers a second time after responseHeadersReady has already fired. Response headers may be mutated exactly once; a duplicate response_headers message is a protocol violation and fails the proc stream.","triggerScenarios":"The server emits two (or more) response_headers ProcessingResponse messages on the same stream.","commonSituations":"Server bug where a retry/loop sends response_headers twice. Server designed to 'update' headers later, which the protocol forbids.","solutions":["Ensure the server sends exactly one response_headers response per RPC.","Track in the server whether headers were already acked and skip subsequent sends.","Enable failure_mode_allow to bypass while remediating."],"exampleFix":"// before: server acks headers on every receive loop\nfor { req := <-stream.Recv(); stream.Send(responseHeadersFor(req)) }\n\n// after: ack headers once\nheadersAcked := false\nfor {\n  req := <-stream.Recv()\n  if req.GetResponseHeaders() != nil && !headersAcked {\n    stream.Send(responseHeadersFor(req)); headersAcked = true\n  }\n}","handlingStrategy":"validation","validationCode":"// Server: send response_headers exactly once.\nrespHeadersSent := false\n// if req is response_headers && !respHeadersSent { Send(...); respHeadersSent = true }","typeGuard":null,"tryCatchPattern":"// Client: failure_mode_allow -> bypass.","preventionTips":["Track a one-shot flag for response_headers in the server.","Avoid re-entrant/retry loops that re-send headers."],"tags":["extproc","protocol-violation","ordering","response-headers","duplicate","server-bug"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}