{"record":{"id":"ae84745a6c5712c1","repo":"grpc/grpc-go","slug":"xds-node-id-v-w","errorCode":null,"errorMessage":"[xDS node id: %v]: %w","messagePattern":"\\[xDS node id: (.+?)\\]: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/xds/balancer/cdsbalancer/cdsbalancer.go","lineNumber":443,"sourceCode":"\t// ExitIdle (but still checks for the interface's existence to\n\t// avoid a panic if not). If the child does not, no subconns\n\t// will be connected.\n\tb.childLB.ExitIdle()\n}\n\n// Node ID needs to be manually added to errors generated in the following\n// scenarios:\n//   - resource-does-not-exist: since the xDS watch API uses a separate callback\n//     instead of returning an error value. TODO(gRFC A88): Once A88 is\n//     implemented, the xDS client will be able to add the node ID to\n//     resource-does-not-exist errors as well, and we can get rid of this\n//     special handling.\n//   - received a good update from the xDS client, but the update either contains\n//     an invalid security configuration or contains invalid aggragate cluster\n//     config.\nfunc (b *cdsBalancer) annotateErrorWithNodeID(err error) error {\n\tnodeID := b.xdsClient.BootstrapConfig().Node().GetId()\n\treturn fmt.Errorf(\"[xDS node id: %v]: %w\", nodeID, err)\n}\n\n// onClusterAmbientError handles an ambient error, if a childLB already has a\n// good update, it should continue using that.\nfunc (b *cdsBalancer) onClusterAmbientError(name string, err error) {\n\tb.logger.Warningf(\"Cluster resource %q received ambient error update: %v\", name, err)\n\n\tif xdsresource.ErrType(err) != xdsresource.ErrorTypeConnection && b.childLB != nil {\n\t\t// Connection errors will be sent to the child balancers directly.\n\t\t// There's no need to forward them.\n\t\tb.childLB.ResolverError(err)\n\t}\n}\n\n// onClusterResourceError handles errors to stop using the previously seen\n// resource. Propagates the error down to the child policy if one exists, and\n// puts the channel in TRANSIENT_FAILURE.\nfunc (b *cdsBalancer) onClusterResourceError(name string, err error) {","sourceCodeStart":425,"sourceCodeEnd":461,"githubUrl":"https://github.com/grpc/grpc-go/blob/03255a9237b6eb32710f6bc4f2de9a675b99fe36/internal/xds/balancer/cdsbalancer/cdsbalancer.go#L425-L461","documentation":"This is the annotateErrorWithNodeID() function which wraps any CDS balancer error with the xDS node ID from the bootstrap config. It is not an error condition itself but rather an error enrichment step. It uses %w verb to preserve the wrapped error for errors.Is/errors.As checking. The node ID helps identify which xDS client node encountered the error, critical for debugging multi-node deployments.","triggerScenarios":"Called by handleXDSConfigUpdate() when a cluster is not found in the xDS config (for static clusters), and by handleClusterUpdate() for any error in outlier detection setup, LB policy unmarshalling, or child config pushing. The function retrieves the node ID from b.xdsClient.BootstrapConfig().Node().GetId() and wraps the original error.","commonSituations":"You see this prefix on any error from the CDS balancer — the actual error is the wrapped part after the node ID. The node ID comes from the xDS bootstrap configuration file. If the node ID is empty or wrong, it indicates a bootstrap configuration problem.","solutions":["Focus on the wrapped error (the part after '[xDS node id: ...]:') — this prefix is just context","Verify the xDS node ID in your bootstrap config matches what you expect for your deployment","If the node ID is empty or default, check your xDS bootstrap configuration file's node.id field","Use errors.Unwrap() or errors.Is() to programmatically access the underlying error"],"exampleFix":"// before: bootstrap config with default/empty node ID\n{\"node\": {\"id\": \"\"}}\n// after: set a meaningful node ID\n{\"node\": {\"id\": \"my-service-instance-1\"}}","handlingStrategy":"try-catch","validationCode":"// Verify xDS bootstrap config has a valid node ID before starting\nfunc validateBootstrapNodeID(bootstrapPath string) error {\n    data, err := os.ReadFile(bootstrapPath)\n    if err != nil {\n        return err\n    }\n    var cfg struct{ Node struct{ Id string `json:\"id\"` } `json:\"node\"` }\n    if err := json.Unmarshal(data, &cfg); err != nil {\n        return err\n    }\n    if cfg.Node.Id == \"\" {\n        return fmt.Errorf(\"xDS bootstrap node.id is empty\")\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"// Use errors.Unwrap to access the underlying error\nvar underlying error\nif inner := errors.Unwrap(err); inner != nil {\n    underlying = inner\n}\n// The node ID prefix is context; the real error is the wrapped part\nif errors.Is(underlying, xdsresource.ErrTypeResourceNotFound) {\n    // handle resource-not-found scenario\n}","preventionTips":["Set a meaningful node ID in the xDS bootstrap config for debugging","Always look at the wrapped error (after the node ID prefix) for the real issue","Use errors.Unwrap or errors.Is for programmatic error handling","Include the node ID in log analysis queries to correlate errors across nodes"],"tags":["xds","cds","node-id","error-annotation","bootstrap"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}