grpc/grpc-go · error

grpctransport: config

Error message

grpctransport: config %q has nil credentials bundle

What it means

Returned when the Config referenced by ConfigName exists but its Credentials field is nil (grpc_transport.go:106). The Builder needs a non-nil credentials.Bundle to construct DialOptions, so a registered-but-empty config is rejected before dialing.

Solutions

  1. Ensure tlscreds.NewBundle (or equivalent) succeeds before registering the Config.
  2. Propagate bundle-construction errors at bootstrap time rather than storing nil.
  3. Add a startup assertion that every Config in the map has non-nil Credentials.

Example fix

// before
b, _, err := tlscreds.NewBundle(cfg)
// err ignored, b may be nil
builder := grpctransport.NewBuilder(map[string]Config{"mtls": {Credentials: nil}})

// after
b, closeFn, err := tlscreds.NewBundle(cfg)
if err != nil { return err }
builder := grpctransport.NewBuilder(map[string]Config{"mtls": {Credentials: b}})
Defensive patterns

Strategy: validation

Validate before calling

for name, cfg := range configs {
    if cfg.Credentials == nil {
        return fmt.Errorf("config %q has nil credentials", name)
    }
}
builder := grpctransport.NewBuilder(configs)

Try / catch

if err != nil && strings.Contains(err.Error(), "nil credentials bundle") {
    // build the bundle first, propagate any error, then register
}

Prevention

When it happens

Trigger: grpctransport.NewBuilder is called with a map entry like "mtls": {Credentials: nil}. Build resolves the config successfully then fails the nil check.

Common situations: Config was registered as a placeholder; tlscreds.NewBundle failed earlier and the caller stored a nil bundle instead of propagating the error; the credentials field was omitted in a struct literal.

Related errors


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

Appendix: source

Thrown at internal/xds/clients/grpctransport/grpc_transport.go:106

// The Extension field of the ServerIdentifier must be a ServerIdentifierExtension.
func (b *Builder) Build(si clients.ServerIdentifier) (clients.Transport, error) {
	if si.ServerURI == "" {
		return nil, fmt.Errorf("grpctransport: ServerURI is not set in ServerIdentifier")
	}
	if si.Extensions == nil {
		return nil, fmt.Errorf("grpctransport: Extensions is not set in ServerIdentifier")
	}
	sce, ok := si.Extensions.(ServerIdentifierExtension)
	if !ok {
		return nil, fmt.Errorf("grpctransport: Extensions field is %T, but must be %T in ServerIdentifier", si.Extensions, ServerIdentifierExtension{})
	}

	config, ok := b.configs[sce.ConfigName]
	if !ok {
		return nil, fmt.Errorf("grpctransport: unknown config name %q specified in ServerIdentifierExtension", sce.ConfigName)
	}
	if config.Credentials == nil {
		return nil, fmt.Errorf("grpctransport: config %q has nil credentials bundle", sce.ConfigName)
	}

	b.mu.Lock()
	defer b.mu.Unlock()

	if cc, ok := b.connections[si]; ok {
		if logger.V(2) {
			logger.Infof("Reusing existing connection to the server for ServerIdentifier: %v", si)
		}
		b.refs[si]++
		tr := &grpcTransport{cc: cc}
		tr.cleanup = b.cleanupFunc(si, tr)
		return tr, nil
	}

	// Create a new gRPC client/channel for the server with the provided
	// credentials, server URI, and a byte codec to send and receive messages.
	// Also set a static keepalive configuration that is common across gRPC

View on GitHub (pinned to 0c51461d27)