We needed cluster-autoscaler to be allowed to manage volumeattachments, a fairly ordinary RBAC tweak. I added an extra rule to the Helm values under rbac.extraRules, ran the upgrade, and helm upgrade reported success: no errors, exit code zero.
Then I checked the actual ClusterRole afterward. The new rule wasn’t there. Not “there but wrong,” just completely absent, like the values change had never happened.
The value went in, and nowhere came out the other side
First instinct is always “I must have gotten the YAML indentation wrong,” so I re-checked the values file three times. It was structurally correct, and it passed schema-less as far as Helm was concerned, because Helm doesn’t validate that every key in your values file actually corresponds to something the chart’s templates use. If you pass a key the chart’s templates never reference, Helm won’t warn you. It just renders the templates without ever touching that key, and reports the same success it would report for a value that mattered.
Running helm template on the chart and dumping the rendered ClusterRole manifest directly confirmed it: no trace of rbac.extraRules anywhere in the output. Not because of a typo, but because that specific chart version’s templates never looked for a key by that name in the first place.
A parameter name that used to be true, on a version that wasn’t running
It turns out rbac.extraRules is a real, documented values key for the cluster-autoscaler chart, just not for the version pinned in this deployment. The chart version in use predated that parameter existing under that name at all. A newer chart version supports additional RBAC rules, but the key it actually reads is rbac.additionalRules. That’s not a small naming quirk; it’s an actual rename between chart releases. The newer version also happens to bake volumeattachments into the default ClusterRole automatically, so the “extra rule” wouldn’t even have been necessary anymore once the upgrade happened.
None of that is discoverable from the values file you’re editing. Your own YAML is internally consistent and looks exactly like a config change that should do something. The chart’s actual template files are a separate piece you’d have to go read directly, or run helm show values <chart> --version X, which prints only the keys that version genuinely recognizes, so you can compare against what you’re about to set.
Why “no error” is the whole problem here
This is a specific and kind of insidious failure mode. Unlike a typo’d field name in a strongly-typed system, an unrecognized key in a values file doesn’t fail the deploy, doesn’t warn in the Helm output, and doesn’t show up as a diff against nothing. helm upgrade genuinely has no mechanism to tell you “I ignored part of what you gave me.” From its point of view, rendering a template just means substituting the keys the template actually asks for, and anything you passed that no template asks for simply never gets read. You get a clean exit code for a change that changed nothing.
That means the failure signal you’re looking for doesn’t exist in the tool that made the mistake. It only exists in the actual output, the rendered manifest or the live object in the cluster, which means you have to check the result, not the process, to know whether a config change landed.
The resolution, and the habit it left behind
The real fix was a chart version bump, from a release still on Kubernetes 1.31 compatibility to one built for 1.33. That brought rbac.additionalRules along with it and made the volumeattachments permission unnecessary as an “extra” rule anyway, since the new default ClusterRole already includes it.
What changed afterward is a small habit: after any Helm values change that’s supposed to affect RBAC, resource limits, or anything else structural, check the rendered output with helm template, or check the live object with kubectl get clusterrole -o yaml, instead of trusting the deploy’s exit code. A successful helm upgrade tells you the templates rendered and applied without a syntax or API error. It does not tell you that every value you set actually reached a template that reads it. Those are two different guarantees, and only one of them gets checked automatically.