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

  1. Set failure_mode_allow: true in the filter config so the dataplane RPC proceeds without ext_proc when the stream cannot be opened.
  2. Check ext_proc server logs for the rejected stream and its RPC error (authz, resource exhaustion, UNIMPLEMENTED).
  3. Verify the server implements envoy.service.ext_proc.v3.ExternalProcessor/Process and that credentials/authority are valid.
  4. 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

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


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)