{"record":{"id":"159c2b73c098cb33","repo":"grpc/grpc-go","slug":"external-processor-returned-unexpected-status-v-f-159c2b","errorCode":null,"errorMessage":"external processor returned unexpected status %v for body response, expected %v","messagePattern":"external processor returned unexpected status (.+?) for body response, expected (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/xds/httpfilter/extproc/ext_proc.go","lineNumber":1524,"sourceCode":"\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\nfunc (cs *clientStream) validateBodyResponse(bodyResp *v3procservicepb.BodyResponse) (*v3procservicepb.StreamedBodyResponse, bool) {\n\tif status := bodyResp.GetResponse().GetStatus(); status != v3procservicepb.CommonResponse_CONTINUE {\n\t\tcs.failProcStream(fmt.Errorf(\"external processor returned unexpected status %v for body response, expected %v\", status, v3procservicepb.CommonResponse_CONTINUE))\n\t\treturn nil, false\n\t}\n\tstreamedResp := bodyResp.GetResponse().GetBodyMutation().GetStreamedResponse()\n\tif streamedResp == nil {\n\t\tcs.failProcStream(fmt.Errorf(\"external processor returned invalid body mutation in body response\"))\n\t\treturn nil, false\n\t}\n\tif streamedResp.GetGrpcMessageCompressed() {\n\t\tcs.failProcStream(fmt.Errorf(\"external processor returned compressed grpc message which is not supported\"))\n\t\treturn nil, false\n\t}\n\treturn streamedResp, true\n}\n\nfunc (cs *clientStream) applyMutations(mutation *v3procservicepb.HeaderMutation, md metadata.MD) error {\n\tif mutation == nil {\n\t\treturn nil\n\t}","sourceCodeStart":1506,"sourceCodeEnd":1542,"githubUrl":"https://github.com/grpc/grpc-go/blob/0c51461d27177d997e14c642fe18c11668fc09a3/internal/xds/httpfilter/extproc/ext_proc.go#L1506-L1542","documentation":"Raised by validateBodyResponse (ext_proc.go:1524) when a request_body or response_body response from the server has a status other than CONTINUE. Body responses must use CommonResponse.CONTINUE; any other status is a protocol violation. failProcStream fails the RPC unless failure_mode_allow bypasses it.","triggerScenarios":"Triggered when bodyResp.GetResponse().GetStatus() (ext_proc.go:1523) != CONTINUE on a body message the client received from the server.","commonSituations":"Server attempts to reset/replace a body via status on the body response (not supported here), reuses header-status logic on the body path, or returns an error status instead of failing the RPC explicitly.","solutions":["On the server, always set status = CommonResponse.CONTINUE on body responses.","If the server needs to abort the RPC, use the immediate_response field with a proper gRPC status instead.","Enable failure_mode_allow so the client tolerates the bad status and bypasses ext_proc.","Separate the header and body response construction so status logic is not copied across paths."],"exampleFix":"// before: body response carries a non-CONTINUE status\n&procpb.BodyResponse{Response: &procpb.CommonResponse{Status: procpb.CommonResponse_RESET, BodyMutation: mut}}\n\n// after: body responses must use CONTINUE\n&procpb.BodyResponse{Response: &procpb.CommonResponse{Status: procpb.CommonResponse_CONTINUE, BodyMutation: mut}}","handlingStrategy":"fallback","validationCode":"// On the ext_proc SERVER: body responses must always carry CONTINUE.\nfunc buildBodyResponse(mut *procpb.BodyMutation) *procpb.BodyResponse {\n    return &procpb.BodyResponse{Response: &procpb.CommonResponse{\n        Status: procpb.CommonResponse_CONTINUE,\n        BodyMutation: mut,\n    }}\n}","typeGuard":null,"tryCatchPattern":"filter.failure_mode_allow = true\nif st, ok := status.FromError(err); ok && st.Code() == codes.Internal &&\n    strings.Contains(st.Message(), \"unexpected status\") &&\n    strings.Contains(st.Message(), \"for body response\") {\n    // server returned a non-CONTINUE status on a body response\n}","preventionTips":["Server: always set status = CONTINUE on body responses.","Use immediate_response to abort the RPC with a proper gRPC status instead.","Enable failure_mode_allow to tolerate the bad status.","Keep header-status and body-status construction separate to avoid copy-paste."],"tags":["grpc","xds","extproc","envoy","protocol-violation","body","status"],"backgroundTag":null,"analyzedSha":"0c51461d27177d997e14c642fe18c11668fc09a3","analyzedAt":"2026-08-11T14:49:15.055Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}