{"record":{"id":"16d52958afc35463","repo":"grpc/grpc-go","slug":"external-processor-sent-response-trailers-before-r","errorCode":null,"errorMessage":"external processor sent response trailers before response trailers were sent to it","messagePattern":"external processor sent response trailers before response trailers were sent to it","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/xds/httpfilter/extproc/ext_proc.go","lineNumber":1337,"sourceCode":"\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()\n\n\t\tcase resp.GetResponseTrailers() != nil:\n\t\t\tif cs.config.processingModes.responseTrailerMode == modeSkip {\n\t\t\t\tcs.failProcStream(fmt.Errorf(\"external processor unexpectedly sent response trailers when response trailer processing is disabled\"))\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !cs.trailerSent.Load() {\n\t\t\t\tcs.failProcStream(fmt.Errorf(\"external processor sent response trailers before response trailers were sent to it\"))\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif cs.responseTrailerReady.HasFired() {\n\t\t\t\tcs.failProcStream(fmt.Errorf(\"external processor unexpectedly sent duplicate response trailers after response trailers were already processed\"))\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttrailer := resp.GetResponseTrailers()\n\t\t\tif err = cs.applyMutations(trailer.GetHeaderMutation(), cs.responseTrailers); 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 trailer is modified and ready to be sent to\n\t\t\t// the client.\n\t\t\tcs.fireResponseTrailerReady()\n\t\t}\n\t}\n}\n","sourceCodeStart":1319,"sourceCodeEnd":1355,"githubUrl":"https://github.com/grpc/grpc-go/blob/03255a9237b6eb32710f6bc4f2de9a675b99fe36/internal/xds/httpfilter/extproc/ext_proc.go#L1319-L1355","documentation":"Raised when the server sends response_trailers before the client has sent response trailers to it (trailerSent is false). The server may only mutate trailers it has first received; an unsolicited response-trailers message is out of order and fails the proc stream.","triggerScenarios":"The server emits a response_trailers ProcessingResponse without first receiving the client's response_trailers ProcessingRequest.","commonSituations":"Server proactively injects trailers at end-of-stream instead of reacting to the client's trailer message. Server confuses request-body EndOfStream with the trailer exchange.","solutions":["Make the server wait for the client's response_trailers message before sending response_trailers.","Track stream lifecycle in the server so trailers are only echoed/mutated once received.","Enable failure_mode_allow to bypass while remediating."],"exampleFix":"// before: server pushes trailers unsolicited\nfunc Process(stream) {\n  stream.Send(&pb.ProcessingResponse{ResponseTrailers: inject})\n}\n\n// after: wait for client trailers\nfor {\n  req, _ := stream.Recv()\n  if req.GetResponseTrailers() != nil {\n    stream.Send(&pb.ProcessingResponse{ResponseTrailers: mutate(req)}); return\n  }\n}","handlingStrategy":"validation","validationCode":"// Server: only send response_trailers after receiving the client's response_trailers.\nseenRespTrailers := false\n// for each req: if req.GetResponseTrailers()!=nil { seenRespTrailers=true }\n// only emit response_trailers when seenRespTrailers","typeGuard":null,"tryCatchPattern":"// Client: failure_mode_allow -> bypass.","preventionTips":["Always wait for the client's response_trailers before mutating trailers.","Track end-of-stream carefully so trailers are exchanged once."],"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"}