grpc/grpc-go · error
failed to create a stream to external processor
Error message
failed to create a stream to external processor: %v
What it means
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).
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.
Example fix
// before: any ext_proc stream-open error fails the user RPC filter.failure_mode_allow = false // after: tolerate ext_proc stream-open failures, fall through to dataplane filter.failure_mode_allow = true
Defensive patterns
Strategy: try-catch
Validate before calling
// No client-side validation prevents a transport-level stream-open failure,
// but you can confirm the channel is READY before issuing critical RPCs.
func waitForExtProcReady(cc *grpc.ClientConn, timeout time.Duration) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
return cc.WaitForStateChange(ctx, connectivity.Ready) // or use cc.Connect()
} Try / catch
// Catch the ext_proc stream-open failure (wrapped in codes.Internal) and,
// if failure_mode_allow is unset, retry or fall back.
err := conn.Invoke(ctx, "/pkg.Svc/Method", req, resp)
if err != nil {
if st, ok := status.FromError(err); ok && st.Code() == codes.Internal &&
strings.Contains(st.Message(), "failed to create a stream to external processor") {
// ext_proc transport failed: retry, or enable failure_mode_allow in config
}
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- extproc: failed to create channel to the external processor…
- failed to send client headers to external processor server
- external processor returned invalid body mutation in body…
- external processor returned unexpected status
- external processor returned unexpected status
AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11).
Data as JSON: /api/errors/4d4f323c97ddbb64.
Report an issue: GitHub.
Appendix: source
Thrown at internal/xds/httpfilter/extproc/ext_proc.go:438
onFinish: onFinish,
}
// In the ClientStream API, outgoing headers are sent immediately when the
// stream is created. Because we need to send or mutate these headers before
// they go over the wire, we must establish the ext_proc stream now rather
// than deferring it.
var err error
procClient := *i.procClient.Value()
if csCommon.procStream, err = procClient.Process(procCtx, grpc.OnFinish(func(error) {
i.procClient.Decrement()
})); err != nil {
// Since Process failed to create the stream, its OnFinish callback will
// not be called, so we must manually decrement the procClient refcount.
// We do not invoke other registered OnFinish call options here because
// NewStream might return an error directly, and it is the caller's
// responsibility to handle cleanup if NewStream fails or returns an error.
i.procClient.Decrement()
return csCommon.handleInitError(fmt.Errorf("failed to create a stream to external processor: %v", err), newStream, opts...)
}
// Observability mode.
if i.config.observabilityMode {
ocs := &observabilityClientStream{
commonStream: csCommon,
procRecvDone: make(chan struct{}),
}
// Defer the closing of ext proc stream by the defined deferred close
// timeout to allow the server to read all messages from the proc stream.
onFinishFunc := func(error) {
time.AfterFunc(ocs.config.deferredCloseTimeout, ocs.procCancel)
}
newOpts := append(opts, grpc.OnFinish(onFinishFunc))
if ocs.dataplaneStream, err = newStream(ocs.ctx, newOpts...); err != nil {
ocs.cancel()View on GitHub (pinned to 0c51461d27)