grpc/grpc-go · error

rls: json marshal failed for child policy config

Error message

rls: json marshal failed for child policy config {%+v}: %v

What it means

Emitted from parseChildPolicyConfigs (config.go:315) when json.Marshal of the augmented child-config map (original entries plus a dummy target field) fails. Because the map was just produced by json.Unmarshal of valid JSON, re-marshaling should not fail; this branch is defensive and effectively unreachable in normal operation.

Solutions

  1. Ensure the child policy config is plain valid JSON with no exotic RawMessage tricks.
  2. Avoid sharing/mutating the config bytes across goroutines while ParseConfig runs.
  3. If reproducible, minimize the child config to the smallest snippet that triggers it and file a bug; this path is not expected to fire in production.
Defensive patterns

Strategy: validation

Validate before calling

func validateChildConfigRoundTrip(raw json.RawMessage) error {
    var m map[string]json.RawMessage
    if err := json.Unmarshal(raw, &m); err != nil {
        return err
    }
    m["__target__"], _ = json.Marshal("placeholder")
    if _, err := json.Marshal(m); err != nil {
        return fmt.Errorf("child config does not round-trip: %w", err)
    }
    return nil
}

Type guard

func childConfigRoundTrips(raw json.RawMessage) bool {
    return validateChildConfigRoundTrip(raw) == nil
}

Prevention

When it happens

Trigger: Only reachable if a json.RawMessage value inside the unmarshaled map is simultaneously valid for unmarshal but invalid for marshal, or via concurrent unsafe mutation during the brief window between unmarshal and marshal in parseChildPolicyConfigs.

Common situations: Essentially a defensive guard; if hit, suspect memory corruption, an unsafe.Pointer-based mutation, or a non-standard encoding/json replacement.

Related errors


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

Appendix: source

Thrown at balancer/rls/config.go:315

		}
		parser, ok := builder.(balancer.ConfigParser)
		if !ok {
			return "", nil, fmt.Errorf("rls: childPolicy %q with config %q does not support config parsing", name, string(rawCfg))
		}

		// To validate child policy configs we do the following:
		// - unmarshal the raw JSON bytes of the child policy config into a map
		// - add an entry with key set to `target_field_name` and a dummy value
		// - marshal the map back to JSON and parse the config using the parser
		// retrieved previously
		var childConfig map[string]json.RawMessage
		if err := json.Unmarshal(rawCfg, &childConfig); err != nil {
			return "", nil, fmt.Errorf("rls: json unmarshal failed for child policy config %q: %v", string(rawCfg), err)
		}
		childConfig[targetFieldName], _ = json.Marshal(dummyChildPolicyTarget)
		jsonCfg, err := json.Marshal(childConfig)
		if err != nil {
			return "", nil, fmt.Errorf("rls: json marshal failed for child policy config {%+v}: %v", childConfig, err)
		}
		if _, err := parser.ParseConfig(jsonCfg); err != nil {
			return "", nil, fmt.Errorf("rls: childPolicy config validation failed: %v", err)
		}
		return name, childConfig, nil
	}
	return "", nil, fmt.Errorf("rls: invalid childPolicy config: no supported policies found in %+v", childPolicies)
}

func convertDuration(d *durationpb.Duration) (time.Duration, error) {
	if d == nil {
		return 0, nil
	}
	return d.AsDuration(), d.CheckValid()
}

View on GitHub (pinned to 0c51461d27)