{"record":{"id":"31481c619bf4ee42","repo":"grpc/grpc-go","slug":"grpc-credentials-bundle-may-not-be-used-with-indi","errorCode":null,"errorMessage":"grpc: credentials.Bundle may not be used with individual TransportCredentials","messagePattern":"grpc: credentials\\.Bundle may not be used with individual TransportCredentials","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"clientconn.go","lineNumber":93,"sourceCode":"\t// errConnIdling indicates the connection is being closed as the channel\n\t// is moving to an idle mode due to inactivity.\n\terrConnIdling = errors.New(\"grpc: the connection is closing due to channel idleness\")\n\t// invalidDefaultServiceConfigErrPrefix is used to prefix the json parsing error for the default\n\t// service config.\n\tinvalidDefaultServiceConfigErrPrefix = \"grpc: the provided default service config is invalid\"\n\t// PickFirstBalancerName is the name of the pick_first balancer.\n\tPickFirstBalancerName = pickfirst.Name\n)\n\n// The following errors are returned from Dial and DialContext\nvar (\n\t// errNoTransportSecurity indicates that there is no transport security\n\t// being set for ClientConn. Users should either set one or explicitly\n\t// call WithInsecure DialOption to disable security.\n\terrNoTransportSecurity = errors.New(\"grpc: no transport security set (use grpc.WithTransportCredentials(insecure.NewCredentials()) explicitly or set credentials)\")\n\t// errTransportCredsAndBundle indicates that creds bundle is used together\n\t// with other individual Transport Credentials.\n\terrTransportCredsAndBundle = errors.New(\"grpc: credentials.Bundle may not be used with individual TransportCredentials\")\n\t// errNoTransportCredsInBundle indicated that the configured creds bundle\n\t// returned a transport credentials which was nil.\n\terrNoTransportCredsInBundle = errors.New(\"grpc: credentials.Bundle must return non-nil transport credentials\")\n\t// errTransportCredentialsMissing indicates that users want to transmit\n\t// security information (e.g., OAuth2 token) which requires secure\n\t// connection on an insecure connection.\n\terrTransportCredentialsMissing = errors.New(\"grpc: the credentials require transport level security (use grpc.WithTransportCredentials() to set)\")\n)\n\nvar (\n\tdisconnectionsMetric = expstats.RegisterInt64Count(expstats.MetricDescriptor{\n\t\tName:           \"grpc.subchannel.disconnections\",\n\t\tDescription:    \"EXPERIMENTAL. Number of times the selected subchannel becomes disconnected.\",\n\t\tUnit:           \"{disconnection}\",\n\t\tLabels:         []string{\"grpc.target\"},\n\t\tOptionalLabels: []string{\"grpc.lb.backend_service\", \"grpc.lb.locality\", \"grpc.disconnect_error\"},\n\t\tDefault:        false,\n\t})","sourceCodeStart":75,"sourceCodeEnd":111,"githubUrl":"https://github.com/grpc/grpc-go/blob/03255a9237b6eb32710f6bc4f2de9a675b99fe36/clientconn.go#L75-L111","documentation":"errTransportCredsAndBundle (clientconn.go:91-93) is returned by validateTransportCredentials (clientconn.go:484-486) when BOTH WithTransportCredentials(...) and WithCredentialsBundle(...) are supplied. These are mutually exclusive ways to provide transport security.","triggerScenarios":"NewClient/Dial is called with both a TransportCredentials and a CredsBundle in the same option set (clientconn.go:484). gRPC cannot decide which transport credentials to use, so it rejects the config.","commonSituations":"Copy-pasting two examples together; mixing google.golang.org/grpc/credentials/oauth bundle with a separate tls.TransportCredentials; migrating partial config where both were left in.","solutions":["Pick ONE source of transport credentials: either WithTransportCredentials(...) or WithCredentialsBundle(...), not both.","If you need OAuth + TLS via a bundle, use a bundle that includes transport credentials (e.g. oauth.TokenSource + TLS) and drop the standalone WithTransportCredentials.","Otherwise keep standalone TLS WithTransportCredentials and pass the per-RPC token via WithPerRPCCredentials instead of a bundle.","Audit your DialOptions list for duplicate/overlapping credential options."],"exampleFix":"// before — both set\ncc, err := grpc.NewClient(target,\n    grpc.WithTransportCredentials(credentials.NewTLS(tlsConf)),\n    grpc.WithCredentialsBundle(oauth.TokenSourceBundle(ctx, ts)))\n// err: grpc: credentials.Bundle may not be used with individual TransportCredentials\n\n// after — bundle provides TLS + per-RPC\ncc, err := grpc.NewClient(target,\n    grpc.WithCredentialsBundle(oauth.TokenSourceBundle(ctx, ts)))","handlingStrategy":"validation","validationCode":"// Enforce mutual exclusivity at config-build time\nfunc buildOpts(tc credentials.TransportCredentials, bundle credentials.Bundle) []grpc.DialOption {\n    if tc != nil && bundle != nil {\n        panic(\"transport creds and bundle are mutually exclusive\")\n    }\n    switch {\n    case tc != nil: return []grpc.DialOption{grpc.WithTransportCredentials(tc)}\n    case bundle != nil: return []grpc.DialOption{grpc.WithCredentialsBundle(bundle)}\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"cc, err := grpc.NewClient(target, opts...)\nif err != nil && strings.Contains(err.Error(), \"Bundle may not be used\") {\n    // remove one of the two conflicting options and retry construction\n}","preventionTips":["Pick exactly one credential source (transport creds OR bundle).","For OAuth+TLS prefer a bundle that bundles transport creds.","Review merged DialOption slices from config layers."],"tags":["security","credentials","config","grpc-go"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}