{"record":{"id":"4d4f323c97ddbb64","repo":"grpc/grpc-go","slug":"failed-to-create-a-stream-to-external-processor","errorCode":null,"errorMessage":"failed to create a stream to external processor: %v","messagePattern":"failed to create a stream to external processor: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/xds/httpfilter/extproc/ext_proc.go","lineNumber":438,"sourceCode":"\t\tonFinish: onFinish,\n\t}\n\n\t// In the ClientStream API, outgoing headers are sent immediately when the\n\t// stream is created. Because we need to send or mutate these headers before\n\t// they go over the wire, we must establish the ext_proc stream now rather\n\t// than deferring it.\n\tvar err error\n\tprocClient := *i.procClient.Value()\n\tif csCommon.procStream, err = procClient.Process(procCtx, grpc.OnFinish(func(error) {\n\t\ti.procClient.Decrement()\n\t})); err != nil {\n\t\t// Since Process failed to create the stream, its OnFinish callback will\n\t\t// not be called, so we must manually decrement the procClient refcount.\n\t\t// We do not invoke other registered OnFinish call options here because\n\t\t// NewStream might return an error directly, and it is the caller's\n\t\t// responsibility to handle cleanup if NewStream fails or returns an error.\n\t\ti.procClient.Decrement()\n\t\treturn csCommon.handleInitError(fmt.Errorf(\"failed to create a stream to external processor: %v\", err), newStream, opts...)\n\t}\n\n\t// Observability mode.\n\tif i.config.observabilityMode {\n\t\tocs := &observabilityClientStream{\n\t\t\tcommonStream: csCommon,\n\t\t\tprocRecvDone: make(chan struct{}),\n\t\t}\n\n\t\t// Defer the closing of ext proc stream by the defined deferred close\n\t\t// timeout to allow the server to read all messages from the proc stream.\n\t\tonFinishFunc := func(error) {\n\t\t\ttime.AfterFunc(ocs.config.deferredCloseTimeout, ocs.procCancel)\n\t\t}\n\t\tnewOpts := append(opts, grpc.OnFinish(onFinishFunc))\n\n\t\tif ocs.dataplaneStream, err = newStream(ocs.ctx, newOpts...); err != nil {\n\t\t\tocs.cancel()","sourceCodeStart":420,"sourceCodeEnd":456,"githubUrl":"https://github.com/grpc/grpc-go/blob/0c51461d27177d997e14c642fe18c11668fc09a3/internal/xds/httpfilter/extproc/ext_proc.go#L420-L456","documentation":"Returned by NewStream (ext_proc.go:438) when procClient.Process(...) cannot open the bidirectional ExternalProcessor_ProcessClient stream to the ext_proc server. It is wrapped via handleInitError, which fails the RPC with codes.Internal unless failure_mode_allow is true (then the dataplane stream is created directly, bypassing ext_proc).","triggerScenarios":"Triggered when the ext_proc connection is up enough to obtain a client but opening the Process() bidi stream fails — connection dropped between dial and stream start, the server rejects the RPC (wrong method/auth), RPC context already canceled, or the channel entered TRANSIENT_FAILURE.","commonSituations":"Ext_proc server restarting mid-call, mTLS handshake succeeding but the server's authz denying the ext_proc service, idle connection reaped between calls, server-side keepalive killing the stream, or an overloaded server refusing new streams.","solutions":["Set failure_mode_allow: true in the filter config so the dataplane RPC proceeds without ext_proc when the stream cannot be opened.","Check ext_proc server logs for the rejected stream and its RPC error (authz, resource exhaustion, UNIMPLEMENTED).","Verify the server implements envoy.service.ext_proc.v3.ExternalProcessor/Process and that credentials/authority are valid.","Increase client-side reconnect/backoff health so transient channel failures recover before the next RPC, and retry the RPC."],"exampleFix":"// before: any ext_proc stream-open error fails the user RPC\nfilter.failure_mode_allow = false\n\n// after: tolerate ext_proc stream-open failures, fall through to dataplane\nfilter.failure_mode_allow = true","handlingStrategy":"try-catch","validationCode":"// No client-side validation prevents a transport-level stream-open failure,\n// but you can confirm the channel is READY before issuing critical RPCs.\nfunc waitForExtProcReady(cc *grpc.ClientConn, timeout time.Duration) error {\n    ctx, cancel := context.WithTimeout(context.Background(), timeout)\n    defer cancel()\n    return cc.WaitForStateChange(ctx, connectivity.Ready) // or use cc.Connect()\n}","typeGuard":null,"tryCatchPattern":"// Catch the ext_proc stream-open failure (wrapped in codes.Internal) and,\n// if failure_mode_allow is unset, retry or fall back.\nerr := conn.Invoke(ctx, \"/pkg.Svc/Method\", req, resp)\nif err != nil {\n    if st, ok := status.FromError(err); ok && st.Code() == codes.Internal &&\n        strings.Contains(st.Message(), \"failed to create a stream to external processor\") {\n        // ext_proc transport failed: retry, or enable failure_mode_allow in config\n    }\n}","preventionTips":["Set failure_mode_allow: true in the filter config so stream-open errors degrade to dataplane instead of failing the RPC.","Keep the ext_proc server reachable and healthy between RPCs.","Use client-side keepalive so idle connections are not reaped before stream creation.","Monitor gRPC client connectivity state transitions for the ext_proc channel."],"tags":["grpc","xds","extproc","envoy","network","stream","failure-mode-allow"],"backgroundTag":null,"analyzedSha":"0c51461d27177d997e14c642fe18c11668fc09a3","analyzedAt":"2026-08-11T14:49:15.055Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}