grpc/grpc-go · error
failed to marshal child policy config %+v: %v
Error message
failed to marshal child policy config %+v: %v
What it means
Emitted from rlsBalancer.buildAndPushChildPolicyConfigs when json.Marshal(config) fails on the map[string]json.RawMessage child policy config (balancer.go:466). The config map was already validated at ParseConfig time, so this branch is largely defensive; it would fire only if a json.RawMessage value in the stored map is not valid JSON, which the parse-time re-marshal normally catches. On failure the affected childPolicyWrapper goes lame-duck (lamify).
Source
Thrown at balancer/rls/balancer.go:467
// buildAndPushChildPolicyConfigs builds the final child policy configuration by
// adding the `targetField` to the base child policy configuration received in
// RLS LB policy configuration. The `targetField` is set to target and
// configuration is pushed to the child policy through the BalancerGroup.
//
// Caller must hold lb.stateMu.
func (b *rlsBalancer) buildAndPushChildPolicyConfigs(target string, newCfg *lbConfig, ccs *balancer.ClientConnState) error {
jsonTarget, err := json.Marshal(target)
if err != nil {
return fmt.Errorf("failed to marshal child policy target %q: %v", target, err)
}
config := newCfg.childPolicyConfig
targetField := newCfg.childPolicyTargetField
config[targetField] = jsonTarget
jsonCfg, err := json.Marshal(config)
if err != nil {
return fmt.Errorf("failed to marshal child policy config %+v: %v", config, err)
}
parser, _ := b.childPolicyBuilder.(balancer.ConfigParser)
parsedCfg, err := parser.ParseConfig(jsonCfg)
if err != nil {
return fmt.Errorf("childPolicy config parsing failed: %v", err)
}
state := balancer.ClientConnState{ResolverState: ccs.ResolverState, BalancerConfig: parsedCfg}
b.logger.Infof("Pushing new state to child policy %q: %+v", target, state)
if err := b.bg.UpdateClientConnState(target, state); err != nil {
b.logger.Warningf("UpdateClientConnState(%q, %+v) failed : %v", target, ccs, err)
}
return nil
}
func (b *rlsBalancer) ResolverError(err error) {
b.bg.ResolverError(err)View on GitHub (pinned to 0c51461d27)
Solutions
- Ensure the lbConfig returned from ParseConfig is treated as immutable; do not mutate childPolicyConfig outside the balancer's own UpdateClientConnState path.
- Run against upstream google.golang.org/grpc rather than a fork that may have removed the parse-time validation step.
- If authoring the child policy config by hand, validate it with a JSON linter and through the child policy's own ParseConfig before shipping.
Example fix
// before: mutating the parsed config externally
rlsCfg.childPolicyConfig["custom"] = json.RawMessage("not-json")
// after: treat parsed config as immutable; only the balancer writes the target field
// (no external mutation) Defensive patterns
Strategy: validation
Validate before calling
// Confirm every RawMessage in the stored child-policy config is valid JSON
// before the runtime path re-marshals it.
func childConfigIsMarshallable(m map[string]json.RawMessage) bool {
for k, v := range m {
if !json.Valid(v) {
return false
}
_ = k
}
_, err := json.Marshal(m)
return err == nil
} Type guard
func isImmutableChildConfig(m map[string]json.RawMessage) bool {
_, err := json.Marshal(m)
return err == nil
} Prevention
- Treat the LoadBalancingConfig returned by ParseConfig as immutable; never mutate childPolicyConfig externally.
- Run the upstream RLS balancer; forks that skip parse-time validation reintroduce this risk.
- Validate hand-authored child configs with json.Valid before deploying.
When it happens
Trigger: The stored childPolicyConfig map contains a json.RawMessage entry that is not valid JSON when re-marshaled at runtime after the target field is overwritten. Reachable in practice only via a race that mutates the stored map between ParseConfig and UpdateClientConnState, or a build/version skew where the stored RawMessage was never parse-validated.
Common situations: Concurrent unsynchronized mutation of the lbConfig.childPolicyConfig map from another goroutine; use of reflect/unsafe to inject raw bytes; a fork of the RLS balancer that skips parse-time validation.
Related errors
- failed to marshal child policy target %q: %v
- rls: json marshal failed for child policy config {%+v}: %v
- rls: json unmarshal failed for child policy config %q: %v
- rls: json unmarshal failed for service config %+v: %v
- rls: childPolicyConfigTargetFieldName field is not set in se
AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11).
Data as JSON: /api/errors/9dbb957e8a1db9e0.
Report an issue: GitHub.