values.schema.json sets additionalProperties: false on global, which makes the chart unusable as a subchart
charts/authup/values.schema.json:1361-1394 declares:
"global": {
"additionalProperties": false,
"properties": {
"defaultStorageClass": { ... },
"imagePullSecrets": { ... },
"imageRegistry": { ... }
},
"required": ["imageRegistry", "imagePullSecrets", "defaultStorageClass"],
"title": "global",
"type": "object"
}Helm copies the parent chart's entireglobal map into every subchart's values (chartutil.CoalesceValues → coalesceGlobals) and then validates those coalesced values against the subchart's own schema (chartutil.ValidateAgainstSchema, which recurses over chrt.Dependencies()). Both happen inside ToRenderValues, i.e. before a single template renders.
So additionalProperties: false on global means: any umbrella chart that puts any key under global other than the three above cannot install authup as a dependency. Not "cannot use the feature" — cannot render at all.
Minimal reproduction
A parent chart with nothing in it but one global key, and authup 0.2.1 in charts/:
# parent/Chart.yamlapiVersion: v2name: parentversion: 0.1.0
# parent/values.yamlglobal:
anythingAtAll: x
$ helm template r parent # helm v3.15.4Error: values don't meet the specifications of the schema(s) in the following chart(s):authup:- global: Additional property anythingAtAll is not allowed
This is not consumer-specific. It fires for global.imagePullPolicy, for Bitnami's global.compatibility.openshift / global.security.allowInsecureImages, for global.clusterDomain, for any org-wide global convention. global is the one node in a values tree that a chart does not own, and closing it with additionalProperties: false asserts ownership the chart does not have.
The concrete consumer: PrivateAIM/flame-hub
flame-hub is replacing its vendored charts/third-party/authup (v0.1.0) with authup/authup 0.2.1. It cannot, today.
flame-hub keeps cross-chart config under global.flameHub.* — and its values.yaml:56-57 states why, verbatim:
# PostgreSQL service and secret, kept under global so subcharts (authup) can read# them: Helm shares only `global` into a subchart's values.postgresql:
host: "postgresql"secretName: "flame-hub-pg"existingSecret: ""
Alongside that: global.flameHub.publicHttps, global.flameHub.ingress.*, global.flameHub.gatewayApi.* (hostname, TLS, external Gateway parentRef, NGF snippets), plus global.imagePullPolicy. 33 keys under global.flameHub in values.yaml, 80 .Values.global.* references across charts/flame-hub/templates, 15 files touching global.flameHub specifically.
The intended subchart wiring — already written against this chart's advertised tpl passthrough:
authup:
database:
host: '{{ include "flameHub.postgresql.host" . }}'existingSecret: '{{ include "flameHub.postgresql.secretName" . }}'trustedOrigins: '{{- if or .Values.global.flameHub.ingress.enabled .Values.global.flameHub.gatewayApi.enabled }}{{ include "flameHub.uiDomain" . }}{{- end }}'And the manifest flame-hub renders itself, next to the subchart, which reads the same globals (charts/flame-hub/templates/authup/httproute.yaml):
{{- if or .Values.global.flameHub.gatewayApi.enabled .Values.authup.gatewayApi.enabled -}}apiVersion: gateway.networking.k8s.io/v1kind: HTTPRoutemetadata:
name: {{ .Release.Name }}-authupspec:
parentRefs:
{{- include "flame-hub.gateway.parentRefs" . | nindent 8 }}hostnames:
- {{ .Values.authup.gatewayApi.hostname | default .Values.global.flameHub.gatewayApi.hostname | quote }}rules:
- matches:
- path:
type: PathPrefixvalue: {{ .Values.authup.gatewayApi.path }}...backendRefs:
- name: {{ .Release.Name }}-authup-server # upstream authup.server.fullnameport: 3000Adding the authup dependency to that chart produces:
Error: values don't meet the specifications of the schema(s) in the following chart(s):authup:- global: Additional property flameHub is not allowed- global: Additional property imagePullPolicy is not allowed
This contradicts the chart's own design
templates/_helpers.tpl:64-73 — authup.tplvalues.render tpl-renders values with "context" $. In a subchart, $ is the subchart's scoped context, so .Values.global.* is the only path from a subchart value to parent config.templates/_database.tpl:16-24 (externalDatabase.host), _database.tpl:58-68 (externalDatabase.existingSecret), _secrets.tpl:22-28 (auth.existingSecret), _database.tpl:88-96 (externalRedis.existingSecret) all route through that helper. That machinery exists specifically so an umbrella can wire authup to shared infrastructure — and the schema forbids the only mechanism that can feed it..agents/references/privateaim-helm.md:32-34 lists "Cross-chart config under global.* + helpers that evaluate in subchart context" as a consumer pattern this chart is built to support.DESIGN.md:377 advertises global.{imageRegistry,imagePullSecrets,defaultStorageClass} as passthrough — correct for what the chart reads, but the schema turns "what I read" into "all that may exist".
Why CI didn't catch it
Makefile's template target only renders standalone:
template:
@for f in charts/authup/ci/*-values.yaml;do\echo"=== $$f";\
helm template test charts/authup -f "$$f">/dev/null ||exit 1;\done
@echo "all ci values render"
No ci values file mentions global (grep -rn global charts/authup/ci/*.yaml → no hits), and nothing installs the chart as a dependency of a parent chart.
Proposed fix — one annotation
values.schema.json is generated by ghcr.io/dadav/helm-schema (make schema), which emits additionalProperties: false by default. The chart already uses the escape hatch elsewhere (e.g. values.yaml:21-24 for commonLabels). So the source change is three comment lines in charts/authup/values.yaml:3-5:
## @section Global parameters# @schema# additionalProperties: true# @schemaglobal:
# -- Global container image registry override (takes precedence over image.registry)imageRegistry: ""# -- Global image pull secrets (list of names or objects)imagePullSecrets: []# -- Global default storage class for dynamic provisioningdefaultStorageClass: ""
then make schema, which flips values.schema.json:1362 to "additionalProperties": true. No new values, no new keys, no template changes. The chart keeps validating the three globals it actually consumes and stops asserting ownership of the ones it doesn't.
Verified: patching only that single property in a local copy of 0.2.1 makes the exact flame-hub wiring render correctly —
global:
imagePullPolicy: IfNotPresentflameHub:
postgresql: {host: my-pg, secretName: my-pg-secret}authup:
postgresql: {enabled: false}externalDatabase:
host: '{{ .Values.global.flameHub.postgresql.host }}'existingSecret: '{{ .Values.global.flameHub.postgresql.secretName }}'# rel-authup-server-env ConfigMapDB_HOST: "my-pg"# rel-authup-server Deployment
- name: DB_PASSWORDvalueFrom:
secretKeyRef:
name: my-pg-secretkey: password
Optionally also drop "global" from the root required array (values.schema.json:3892-3893) so authup: {global: null} stays legal for consumers who want to block global propagation outright — but with additionalProperties: true that is no longer needed.
Worth adding a regression test: a tiny parent chart under charts/authup/ci/ (or a make template step) that depends on authup and sets an unrelated global.someKey, so this class of breakage fails CI rather than a downstream user's install.
Why there is no acceptable workaround
Everything below was tested against 0.2.1 with helm v3.15.4.
| Attempt | Result |
|---|
authup.global.flameHub: null | coalesce.go:161: Conflict: cannot merge map onto non-map for "flameHub". Skipping. — key survives as null, still an additional property. Same error. |
authup.global: null | Stops the copy (coalesce.go:138: warning: skipping globals because destination global is not a table) but then fails - (root): global is required. Would also disable imageRegistry/imagePullSecrets/defaultStorageClass. |
extraDeploy / extraVolumes / extraEnvVars / server.config / existingConfigMap / commonAnnotations / parent rendering its own manifests | Irrelevant. Schema validation runs before any template renders; the failure is that the parent has globals, not what the subchart does with them. |
| Per-subchart global scoping in Helm | Does not exist. coalesceGlobals copies the map unconditionally; there is no allowlist and no inverse of import-values. |
--skip-schema-validation | Helm ≥3.16 only (absent in 3.15.4). Disables validation for the whole release tree — flame-hub's own values plus bitnami redis/rabbitmq/grafana/prometheus, harbor, seaweedfs. Cannot be declared by the chart; every operator, CI job, Argo CD and Flux config must opt in by hand. |
Vendor the chart and rm values.schema.json | Works, but forks the published artifact, breaks helm dependency update against https://helm.authup.org, and re-creates the vendoring burden the migration exists to remove. |
Delete global.flameHub, duplicate literals per subchart | Not expensive — impossible. flame-hub/values.yaml:194-195,203 pass tpl strings that are evaluated in the subchart's context, where global is the only reachable parent state. Without global.flameHub those values cannot be expressed at all, templated or static. flame-hub already carries flameHub.validateNames guards (charts/flame-hub/templates/_helpers.tpl:154-181) as the cost of exactly this duplication for goharbor's un-templatable literals; this would multiply that across every shared value. |
One deleted line in the generated schema (three comment lines in the source) unblocks the chart for every umbrella consumer.
values.schema.jsonsetsadditionalProperties: falseonglobal, which makes the chart unusable as a subchartcharts/authup/values.schema.json:1361-1394declares:Helm copies the parent chart's entire
globalmap into every subchart's values (chartutil.CoalesceValues→coalesceGlobals) and then validates those coalesced values against the subchart's own schema (chartutil.ValidateAgainstSchema, which recurses overchrt.Dependencies()). Both happen insideToRenderValues, i.e. before a single template renders.So
additionalProperties: falseonglobalmeans: any umbrella chart that puts any key underglobalother than the three above cannot install authup as a dependency. Not "cannot use the feature" — cannot render at all.Minimal reproduction
A parent chart with nothing in it but one global key, and authup 0.2.1 in
charts/:This is not consumer-specific. It fires for
global.imagePullPolicy, for Bitnami'sglobal.compatibility.openshift/global.security.allowInsecureImages, forglobal.clusterDomain, for any org-wide global convention.globalis the one node in a values tree that a chart does not own, and closing it withadditionalProperties: falseasserts ownership the chart does not have.The concrete consumer: PrivateAIM/flame-hub
flame-hub is replacing its vendored
charts/third-party/authup(v0.1.0) withauthup/authup0.2.1. It cannot, today.flame-hub keeps cross-chart config under
global.flameHub.*— and itsvalues.yaml:56-57states why, verbatim:Alongside that:
global.flameHub.publicHttps,global.flameHub.ingress.*,global.flameHub.gatewayApi.*(hostname, TLS, external GatewayparentRef, NGF snippets), plusglobal.imagePullPolicy. 33 keys underglobal.flameHubinvalues.yaml, 80.Values.global.*references acrosscharts/flame-hub/templates, 15 files touchingglobal.flameHubspecifically.The intended subchart wiring — already written against this chart's advertised tpl passthrough:
And the manifest flame-hub renders itself, next to the subchart, which reads the same globals (
charts/flame-hub/templates/authup/httproute.yaml):Adding the authup dependency to that chart produces:
This contradicts the chart's own design
templates/_helpers.tpl:64-73—authup.tplvalues.rendertpl-renders values with"context" $. In a subchart,$is the subchart's scoped context, so.Values.global.*is the only path from a subchart value to parent config.templates/_database.tpl:16-24(externalDatabase.host),_database.tpl:58-68(externalDatabase.existingSecret),_secrets.tpl:22-28(auth.existingSecret),_database.tpl:88-96(externalRedis.existingSecret) all route through that helper. That machinery exists specifically so an umbrella can wire authup to shared infrastructure — and the schema forbids the only mechanism that can feed it..agents/references/privateaim-helm.md:32-34lists "Cross-chart config underglobal.*+ helpers that evaluate in subchart context" as a consumer pattern this chart is built to support.DESIGN.md:377advertisesglobal.{imageRegistry,imagePullSecrets,defaultStorageClass}as passthrough — correct for what the chart reads, but the schema turns "what I read" into "all that may exist".Why CI didn't catch it
Makefile'stemplatetarget only renders standalone:No ci values file mentions
global(grep -rn global charts/authup/ci/*.yaml→ no hits), and nothing installs the chart as a dependency of a parent chart.Proposed fix — one annotation
values.schema.jsonis generated byghcr.io/dadav/helm-schema(make schema), which emitsadditionalProperties: falseby default. The chart already uses the escape hatch elsewhere (e.g.values.yaml:21-24forcommonLabels). So the source change is three comment lines incharts/authup/values.yaml:3-5:then
make schema, which flipsvalues.schema.json:1362to"additionalProperties": true. No new values, no new keys, no template changes. The chart keeps validating the three globals it actually consumes and stops asserting ownership of the ones it doesn't.Verified: patching only that single property in a local copy of 0.2.1 makes the exact flame-hub wiring render correctly —
Optionally also drop
"global"from the rootrequiredarray (values.schema.json:3892-3893) soauthup: {global: null}stays legal for consumers who want to block global propagation outright — but withadditionalProperties: truethat is no longer needed.Worth adding a regression test: a tiny parent chart under
charts/authup/ci/(or amake templatestep) that depends on authup and sets an unrelatedglobal.someKey, so this class of breakage fails CI rather than a downstream user's install.Why there is no acceptable workaround
Everything below was tested against 0.2.1 with helm v3.15.4.
authup.global.flameHub: nullcoalesce.go:161: Conflict: cannot merge map onto non-map for "flameHub". Skipping.— key survives asnull, still an additional property. Same error.authup.global: nullcoalesce.go:138: warning: skipping globals because destination global is not a table) but then fails- (root): global is required. Would also disableimageRegistry/imagePullSecrets/defaultStorageClass.extraDeploy/extraVolumes/extraEnvVars/server.config/existingConfigMap/commonAnnotations/ parent rendering its own manifestscoalesceGlobalscopies the map unconditionally; there is no allowlist and no inverse ofimport-values.--skip-schema-validationrm values.schema.jsonhelm dependency updateagainst https://helm.authup.org, and re-creates the vendoring burden the migration exists to remove.global.flameHub, duplicate literals per subchartflame-hub/values.yaml:194-195,203pass tpl strings that are evaluated in the subchart's context, whereglobalis the only reachable parent state. Withoutglobal.flameHubthose values cannot be expressed at all, templated or static. flame-hub already carriesflameHub.validateNamesguards (charts/flame-hub/templates/_helpers.tpl:154-181) as the cost of exactly this duplication for goharbor's un-templatable literals; this would multiply that across every shared value.One deleted line in the generated schema (three comment lines in the source) unblocks the chart for every umbrella consumer.