{"record":{"id":"9dbb957e8a1db9e0","repo":"grpc/grpc-go","slug":"failed-to-marshal-child-policy-config-v-v","errorCode":null,"errorMessage":"failed to marshal child policy config %+v: %v","messagePattern":"failed to marshal child policy config %\\+v: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"balancer/rls/balancer.go","lineNumber":467,"sourceCode":"\n// buildAndPushChildPolicyConfigs builds the final child policy configuration by\n// adding the `targetField` to the base child policy configuration received in\n// RLS LB policy configuration. The `targetField` is set to target and\n// configuration is pushed to the child policy through the BalancerGroup.\n//\n// Caller must hold lb.stateMu.\nfunc (b *rlsBalancer) buildAndPushChildPolicyConfigs(target string, newCfg *lbConfig, ccs *balancer.ClientConnState) error {\n\tjsonTarget, err := json.Marshal(target)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to marshal child policy target %q: %v\", target, err)\n\t}\n\n\tconfig := newCfg.childPolicyConfig\n\ttargetField := newCfg.childPolicyTargetField\n\tconfig[targetField] = jsonTarget\n\tjsonCfg, err := json.Marshal(config)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to marshal child policy config %+v: %v\", config, err)\n\t}\n\n\tparser, _ := b.childPolicyBuilder.(balancer.ConfigParser)\n\tparsedCfg, err := parser.ParseConfig(jsonCfg)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"childPolicy config parsing failed: %v\", err)\n\t}\n\n\tstate := balancer.ClientConnState{ResolverState: ccs.ResolverState, BalancerConfig: parsedCfg}\n\tb.logger.Infof(\"Pushing new state to child policy %q: %+v\", target, state)\n\tif err := b.bg.UpdateClientConnState(target, state); err != nil {\n\t\tb.logger.Warningf(\"UpdateClientConnState(%q, %+v) failed : %v\", target, ccs, err)\n\t}\n\treturn nil\n}\n\nfunc (b *rlsBalancer) ResolverError(err error) {\n\tb.bg.ResolverError(err)","sourceCodeStart":449,"sourceCodeEnd":485,"githubUrl":"https://github.com/grpc/grpc-go/blob/0c51461d27177d997e14c642fe18c11668fc09a3/balancer/rls/balancer.go#L449-L485","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: mutating the parsed config externally\nrlsCfg.childPolicyConfig[\"custom\"] = json.RawMessage(\"not-json\")\n\n// after: treat parsed config as immutable; only the balancer writes the target field\n// (no external mutation)","handlingStrategy":"validation","validationCode":"// Confirm every RawMessage in the stored child-policy config is valid JSON\n// before the runtime path re-marshals it.\nfunc childConfigIsMarshallable(m map[string]json.RawMessage) bool {\n    for k, v := range m {\n        if !json.Valid(v) {\n            return false\n        }\n        _ = k\n    }\n    _, err := json.Marshal(m)\n    return err == nil\n}","typeGuard":"func isImmutableChildConfig(m map[string]json.RawMessage) bool {\n    _, err := json.Marshal(m)\n    return err == nil\n}","tryCatchPattern":null,"preventionTips":["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."],"tags":["rls","child-policy","json","marshalling","concurrency"],"backgroundTag":null,"analyzedSha":"0c51461d27177d997e14c642fe18c11668fc09a3","analyzedAt":"2026-08-11T14:49:15.055Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}