grpc/grpc-go · error
xds: unable to unmarshal LBconfig
Error message
xds: unable to unmarshal LBconfig: %s, error: %v
What it means
ParseConfig for outlier_detection json.Unmarshal's the raw bytes into *LBConfig (balancer.go:111-113). If unmarshal fails (malformed JSON, wrong field types, invalid duration string, unknown enum), parsing is aborted and the original bytes plus error are returned.
Solutions
- Validate the raw JSON with a JSON parser before calling ParseConfig.
- Cross-check field types against the LBConfig struct (Interval is a duration string, percentages are uint32).
- For xDS deployments, investigate why xdsclient let the malformed resource through.
Example fix
// before
rawJSON := []byte(`{"interval": "not-a-duration"}`)
cfg, err := outlierdetection.ParseConfig(rawJSON) // errors
// after
rawJSON := []byte(`{"interval": "10s"}`)
cfg, err := outlierdetection.ParseConfig(rawJSON) Defensive patterns
Strategy: validation
Validate before calling
// Validate raw JSON before calling ParseConfig.
func validateOutlierDetectionJSON(raw json.RawMessage) error {
var probe map[string]json.RawMessage
if err := json.Unmarshal(raw, &probe); err != nil {
return fmt.Errorf("outlier_detection config is not valid JSON: %w", err)
}
return nil
} Prevention
- Schema-validate the outlier_detection JSON before passing it to ParseConfig.
- Use the A50 defaults so only fields you intentionally override are present.
- For xDS deployments, monitor NACKs which indicate the xdsclient failed to validate upstream.
When it happens
Trigger: raw JSON is syntactically invalid, contains fields with wrong types (e.g. interval as a non-duration string), or has structurally incompatible nested objects (child policy config of wrong shape).
Common situations: Manually constructed outlier_detection JSON with typos; control plane sending malformed config (xdsclient should catch this upstream, so this is rare in production); test fixtures with bad JSON; field type drift after a gRPC upgrade.
Related errors
- failed to JSON marshal load balancing policy for child
- failed to parse load balancing policy for child
- outlier detection: child balancer
- outlier detection: error switching to child of type
- child policy not registered
AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11).
Data as JSON: /api/errors/d2410ca77bfbc5a9.
Report an issue: GitHub.
Appendix: source
Thrown at internal/xds/balancer/outlierdetection/balancer.go:112
b.logger.Infof("Created")
b.child = synchronizingBalancerWrapper{lb: gracefulswitch.NewBalancer(b, bOpts)}
go b.run()
return b
}
func (bb) ParseConfig(s json.RawMessage) (serviceconfig.LoadBalancingConfig, error) {
lbCfg := &LBConfig{
// Default top layer values as documented in A50.
Interval: iserviceconfig.Duration(10 * time.Second),
BaseEjectionTime: iserviceconfig.Duration(30 * time.Second),
MaxEjectionTime: iserviceconfig.Duration(300 * time.Second),
MaxEjectionPercent: 10,
}
// This unmarshalling handles underlying layers sre and fpe which have their
// own defaults for their fields if either sre or fpe are present.
if err := json.Unmarshal(s, lbCfg); err != nil { // Validates child config if present as well.
return nil, fmt.Errorf("xds: unable to unmarshal LBconfig: %s, error: %v", string(s), err)
}
// Note: in the xds flow, these validations will never fail. The xdsclient
// performs the same validations as here on the xds Outlier Detection
// resource before parsing resource into JSON which this function gets
// called with. A50 defines two separate places for these validations to
// take place, the xdsclient and this ParseConfig method. "When parsing a
// config from JSON, if any of these requirements is violated, that should
// be treated as a parsing error." - A50
switch {
// "The google.protobuf.Duration fields interval, base_ejection_time, and
// max_ejection_time must obey the restrictions in the
// google.protobuf.Duration documentation and they must have non-negative
// values." - A50
// Approximately 290 years is the maximum time that time.Duration (int64)
// can represent. The restrictions on the protobuf.Duration field are to be
// within +-10000 years. Thus, just check for negative values.
case lbCfg.Interval < 0:View on GitHub (pinned to 0c51461d27)