grpc/grpc-go · error

xdsclient: clientConfig is nil

Error message

xdsclient: clientConfig is nil

What it means

`newXDSChannel` (channel.go:79) also requires `clientConfig` (*Config) — the full xDS client configuration used to decode resources. The channel uses it (including its Node proto) when building ADS requests and when interpreting resource responses. A nil clientConfig fails at channel.go:85-86.

Source

Thrown at internal/xds/clients/xdsclient/channel.go:86

	serverConfig       *ServerConfig           // Configuration of the server to connect to.
	clientConfig       *Config                 // Complete xDS client configuration, used to decode resources.
	eventHandler       xdsChannelEventHandler  // Callbacks for ADS stream events.
	backoff            func(int) time.Duration // Backoff function to use for stream retries. Defaults to exponential backoff, if unset.
	watchExpiryTimeout time.Duration           // Timeout for ADS resource watch expiry.
	logPrefix          string                  // Prefix to use for logging.
}

// newXDSChannel creates a new xdsChannel instance with the provided options.
// It performs basic validation on the provided options and initializes the
// xdsChannel with the necessary components.
func newXDSChannel(opts xdsChannelOpts) (*xdsChannel, error) {
	switch {
	case opts.transport == nil:
		return nil, errors.New("xdsclient: transport is nil")
	case opts.serverConfig == nil:
		return nil, errors.New("xdsclient: serverConfig is nil")
	case opts.clientConfig == nil:
		return nil, errors.New("xdsclient: clientConfig is nil")
	case opts.eventHandler == nil:
		return nil, errors.New("xdsclient: eventHandler is nil")
	}

	xc := &xdsChannel{
		transport:    opts.transport,
		serverConfig: opts.serverConfig,
		clientConfig: opts.clientConfig,
		eventHandler: opts.eventHandler,
		closed:       syncutil.NewEvent(),
	}

	l := grpclog.Component("xds")
	logPrefix := opts.logPrefix + fmt.Sprintf("[xds-channel %p] ", xc)
	xc.logger = igrpclog.NewPrefixLogger(l, logPrefix)

	if opts.backoff == nil {
		opts.backoff = backoff.DefaultExponential.Backoff

View on GitHub (pinned to 0c51461d27)

Solutions

  1. Always pass the XDSClient's stored *Config (c.config) into xdsChannelOpts.clientConfig.
  2. If you write your own channel factory, validate clientConfig is non-nil at the entry point.
  3. Cover channel construction with a unit test that asserts the error path for a nil clientConfig.

Example fix

// before
opts := xdsChannelOpts{transport: t, serverConfig: sc, eventHandler: h}
xc, err := newXDSChannel(opts) // err: clientConfig is nil

// after
opts := xdsChannelOpts{transport: t, serverConfig: sc, clientConfig: c.config, eventHandler: h}
xc, err := newXDSChannel(opts)
Defensive patterns

Strategy: validation

Validate before calling

// Ensure the client's own config is wired through.
func (c *XDSClient) safeNewChannel(opts xdsChannelOpts) (*xdsChannel, error) {
    if c.config == nil {
        return nil, errors.New("cannot build xDS channel: client config is nil")
    }
    opts.clientConfig = c.config
    return newXDSChannel(opts)
}

Prevention

When it happens

Trigger: Triggered when `newXDSChannel` is invoked with `opts.clientConfig == nil`. As with the other channel.go guards, this is an internal-API misuse: the XDSClient is expected to forward its own *Config.

Common situations: A refactor that drops the clientConfig field when assembling xdsChannelOpts; a custom XDSClient implementation that does not hold a Config; tests that build opts manually without copying the client's Config.

Related errors


AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11). Data as JSON: /api/errors/321db6ec7ac45b98. Report an issue: GitHub.