{"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":"exception","errorClass":null,"httpStatus":null,"severity":"info","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/0c51461d27177d997e14c642fe18c11668fc09a3/internal/xds/balancer/cdsbalancer/cdsbalancer.go#L425-L461","documentation":"This is the error format string used by annotateErrorWithNodeID (line 443) to wrap xDS-related errors with the bootstrap node ID for diagnostics. The %w verb wraps the original error (preserving errors.Is/As chains) and %v inserts the node ID from b.xdsClient.BootstrapConfig().Node().GetId(). It is not an error itself but a diagnostic prefix applied to errors 345, 346, 347, and any error from handleClusterUpdate via line 259. The node ID helps correlate client-side errors with xDS server logs.","triggerScenarios":"Any error path in handleXDSConfigUpdate or handleClusterUpdate that calls annotateErrorWithNodeID: missing cluster (345), outlier detection failure (346), LB policy unmarshal failure (347), or child config update failure (line 259). The wrapper adds '[xDS node id: <id>]:' prefix to the original error message.","commonSituations":"This prefix appears whenever a CDS balancer encounters a configuration error tied to a specific cluster resource. The node ID value is critical for debugging — it identifies which xDS client (from the bootstrap config) generated the error, useful in multi-tenant or multi-server setups.","solutions":["Look past the '[xDS node id: ...]:' prefix to find the actual underlying error and remediate that specific issue.","Use the node ID to correlate with xDS management server logs — search for this node ID in the server's request/response logs.","Verify the node ID in your bootstrap configuration matches expectations (it should be unique per client instance).","If using errors.Is/As to programmatically detect the inner error, note that %w preserves the wrapping chain so these still work."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// This is an error wrapper, not an error type. The underlying error\n// is preserved via %w. Use errors.Is/As to detect the inner error:\nvar clusterNotFoundErr error // define sentinel if needed\nif errors.Is(err, clusterNotFoundErr) {\n    // handle missing cluster\n}\n// Extract node ID from the message for correlation:\nif strings.HasPrefix(err.Error(), \"[xDS node id:\") {\n    parts := strings.SplitN(err.Error(), \"]:\", 2)\n    nodeID := strings.TrimPrefix(parts[0], \"[xDS node id: \")\n    log.Printf(\"xDS error from node %s: %s\", nodeID, parts[1])\n}","preventionTips":["Always look past the '[xDS node id:]' prefix to find the root cause.","Use the node ID to correlate with xDS server-side logs.","Verify the bootstrap config's node ID is unique and identifiable per deployment."],"tags":["xds","error-wrapper","diagnostics","node-id","cds"],"backgroundTag":null,"analyzedSha":"0c51461d27177d997e14c642fe18c11668fc09a3","analyzedAt":"2026-08-11T14:49:15.055Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}