grpc/grpc-go · info

[xDS node id: ]

Error message

[xDS node id: %v]: %w

What it means

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.

Solutions

  1. Look past the '[xDS node id: ...]:' prefix to find the actual underlying error and remediate that specific issue.
  2. Use the node ID to correlate with xDS management server logs — search for this node ID in the server's request/response logs.
  3. Verify the node ID in your bootstrap configuration matches expectations (it should be unique per client instance).
  4. If using errors.Is/As to programmatically detect the inner error, note that %w preserves the wrapping chain so these still work.
Defensive patterns

Strategy: try-catch

Try / catch

// This is an error wrapper, not an error type. The underlying error
// is preserved via %w. Use errors.Is/As to detect the inner error:
var clusterNotFoundErr error // define sentinel if needed
if errors.Is(err, clusterNotFoundErr) {
    // handle missing cluster
}
// Extract node ID from the message for correlation:
if strings.HasPrefix(err.Error(), "[xDS node id:") {
    parts := strings.SplitN(err.Error(), "]:", 2)
    nodeID := strings.TrimPrefix(parts[0], "[xDS node id: ")
    log.Printf("xDS error from node %s: %s", nodeID, parts[1])
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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

Appendix: source

Thrown at internal/xds/balancer/cdsbalancer/cdsbalancer.go:443

	// ExitIdle (but still checks for the interface's existence to
	// avoid a panic if not). If the child does not, no subconns
	// will be connected.
	b.childLB.ExitIdle()
}

// Node ID needs to be manually added to errors generated in the following
// scenarios:
//   - resource-does-not-exist: since the xDS watch API uses a separate callback
//     instead of returning an error value. TODO(gRFC A88): Once A88 is
//     implemented, the xDS client will be able to add the node ID to
//     resource-does-not-exist errors as well, and we can get rid of this
//     special handling.
//   - received a good update from the xDS client, but the update either contains
//     an invalid security configuration or contains invalid aggragate cluster
//     config.
func (b *cdsBalancer) annotateErrorWithNodeID(err error) error {
	nodeID := b.xdsClient.BootstrapConfig().Node().GetId()
	return fmt.Errorf("[xDS node id: %v]: %w", nodeID, err)
}

// onClusterAmbientError handles an ambient error, if a childLB already has a
// good update, it should continue using that.
func (b *cdsBalancer) onClusterAmbientError(name string, err error) {
	b.logger.Warningf("Cluster resource %q received ambient error update: %v", name, err)

	if xdsresource.ErrType(err) != xdsresource.ErrorTypeConnection && b.childLB != nil {
		// Connection errors will be sent to the child balancers directly.
		// There's no need to forward them.
		b.childLB.ResolverError(err)
	}
}

// onClusterResourceError handles errors to stop using the previously seen
// resource. Propagates the error down to the child policy if one exists, and
// puts the channel in TRANSIENT_FAILURE.
func (b *cdsBalancer) onClusterResourceError(name string, err error) {

View on GitHub (pinned to 0c51461d27)