grpc/grpc-go · error
xds: failed to create a new channel for server config
Error message
xds: failed to create a new channel for server config %v: %v
What it means
newXDSChannel returned an error when constructing the xdsChannel for a server config. newXDSChannel (channel.go:79-89) validates that transport, serverConfig, clientConfig, and eventHandler are all non-nil. This wraps that internal validation error, meaning one of the required channel components was nil at creation time.
Solutions
- If using a custom TransportBuilder, verify Build never returns (nil, nil)
- Ensure the XDSClient is not used after Close() returns
- If this occurs with unmodified gRPC, report it as a bug with a goroutine dump
Defensive patterns
Strategy: validation
Validate before calling
// Wrap TransportBuilder to guard against nil-transport-with-nil-error returns.
type safeTransportBuilder struct{ inner clients.TransportBuilder }
func (s safeTransportBuilder) Build(si clients.ServerIdentifier) (clients.Transport, error) {
t, err := s.inner.Build(si)
if err == nil && t == nil {
return nil, fmt.Errorf("TransportBuilder returned nil transport for %v", si)
}
return t, err
}
config.TransportBuilder = safeTransportBuilder{inner: config.TransportBuilder} Prevention
- Do not call xDS client methods after Close() returns
- Wrap custom TransportBuilders with a nil-return guard to catch implementation bugs
- Avoid concurrent Close() and resource watch operations on the same client instance
When it happens
Trigger: Called right after a transport is successfully built at xdsclient.go:289, but newXDSChannel at line 298 rejects one of the xdsChannelOpts fields as nil (channel.go:81-88). Since transport was just built and serverConfig/clientConfig come from the already-validated XDSClient, this is an internal invariant violation.
Common situations: A concurrent Close() racing with channel creation causing a field to become nil; a custom TransportBuilder returning (nil, nil) that bypasses the Build error check; internal gRPC bug in non-standard usage. Extremely rare in standard gRPC xDS client operation.
Related errors
- extproc: error parsing config
- extproc: error parsing override
- rls_csp: error marshaling load balancing config
- xds_wrr_locality: error marshalling prepared config
- all SubConns are in TransientFailure
AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11).
Data as JSON: /api/errors/0f8b1d6dad864a15.
Report an issue: GitHub.
Appendix: source
Thrown at internal/xds/clients/xdsclient/xdsclient.go:308
if err != nil {
return nil, func() {}, fmt.Errorf("xds: failed to create transport for server config %v: %v", serverConfig, err)
}
state := &channelState{
parent: c,
serverConfig: serverConfig,
interestedAuthorities: make(map[*authority]bool),
}
channel, err := newXDSChannel(xdsChannelOpts{
transport: tr,
serverConfig: serverConfig,
clientConfig: c.config,
eventHandler: state,
backoff: c.backoff,
watchExpiryTimeout: c.watchExpiryTimeout,
logPrefix: clientPrefix(c),
})
if err != nil {
return nil, func() {}, fmt.Errorf("xds: failed to create a new channel for server config %v: %v", serverConfig, err)
}
state.channel = channel
c.xdsActiveChannels[*serverConfig] = state
initLocked(state)
return state.channel, c.releaseChannel(serverConfig, state, deInitLocked), nil
}
// releaseChannel is a function that is called when a reference to an xdsChannel
// needs to be released. It handles closing channels with no active references.
//
// The function takes the following parameters:
// - serverConfig: the server configuration for the xdsChannel
// - state: the state of the xdsChannel
// - deInitLocked: a function that performs any necessary cleanup for the xdsChannel
//
// The function returns another function that can be called to release the
// reference to the xdsChannel. This returned function is idempotent, meaning
// it can be called multiple times without any additional effect.View on GitHub (pinned to 0c51461d27)