Describe the Bug
Since v1.212.0 (PR #2201 — native deep merge replacing mergo), a nested map whose parent key is an unquoted integer in YAML (e.g. eni.1) is replaced instead of recursively deep-merged when a stack overrides a subset of its keys.
With mergo (≤ v1.211.0) the two maps were deep-merged — keys present only in the catalog were preserved alongside keys present only in the stack override. With the native implementation the catalog keys are silently dropped.
The partial regression fix in PR #2248 addressed list → map type mismatches, but did not address the case where both sides are maps but the parent key is parsed as a YAML integer.
Expected Behavior
Given a catalog that defines:
# catalog/nss.yaml
components:
terraform:
ec2-custom-nss-web:
vars:
eni:
1: # integer key
description: NSS system interface
sg_rules:
ip4_cidr_blocks:
egress,tcp,443: [0.0.0.0/0]
And a stack that adds keys to the same eni.1 entry:
# stack/ue1-pci-it.yaml
components:
terraform:
ec2-custom-nss-web:
vars:
eni:
1:
security_groups:
- sg-07dd44f6e72142c44
sg_rules:
ip4_cidr_blocks:
egress,tcp,8601: [10.107.0.0/17]
atmos describe component ec2-custom-nss-web -s ue1-pci-it should produce a merged eni.1:
eni:
1:
description: NSS system interface # ← preserved from catalog
security_groups:
- sg-07dd44f6e72142c44
sg_rules:
ip4_cidr_blocks:
egress,tcp,443: [0.0.0.0/0] # ← preserved from catalog
egress,tcp,8601: [10.107.0.0/17]
Actual result (v1.212.0): description and egress,tcp,443 are dropped — the stack's eni.1 entirely replaces the catalog's eni.1:
eni:
1:
security_groups:
- sg-07dd44f6e72142c44
sg_rules:
ip4_cidr_blocks:
egress,tcp,8601: [10.107.0.0/17]
Steps to Reproduce
- Create a catalog file with a nested map under an integer YAML key:
# stacks/catalog/base.yaml
components:
terraform:
my-component:
vars:
config:
1:
from_catalog: true
shared_key: catalog_value
- Create a stack file that adds/overrides a subset of keys under the same integer key:
# stacks/env/my-stack.yaml
import:
- catalog/base
components:
terraform:
my-component:
vars:
config:
1:
from_stack: true
shared_key: stack_value
- Run:
atmos describe component my-component -s my-stack
- Observe
config.1 in the output.
v1.211.0: config.1 contains from_catalog: true, from_stack: true, shared_key: stack_value (deep merge).
v1.212.0: config.1 contains only from_stack: true, shared_key: stack_value — from_catalog is absent (replace).
Workaround: Quoting the key ("1":) in both files forces a string key and the native merge handles it correctly, as deepMergeNative only operates on map[string]any.
Screenshots
N/A — see Steps to Reproduce output above.
Environment
- OS: Linux (Geodesic-based infra container)
- Atmos version: v1.212.0 (regression), v1.211.0 (works correctly)
- Terraform version: n/a (issue is in
atmos describe component, no Terraform invocation needed)
Additional Context
Root cause hypothesis:
Go's yaml.v3 parses an unquoted integer key (1:) as int(1), not string("1"). Before merging, Atmos normalizes maps to map[string]any. If this normalization runs correctly, the key becomes "1" and the native merge should recurse into it. However, if any step in the pipeline between YAML unmarshaling and deepMergeNative skips normalization, the map may arrive as map[interface{}]any or map[int]any. The native merge's type switch only matches map[string]any; an unrecognized map type falls through as a scalar leaf and is replaced rather than recursed into.
Mergo used reflection and could merge any concrete map type, masking this normalization gap.
Related PRs:
Workaround available: Quote all integer YAML keys ("1": instead of 1:). This is safe but requires touching every catalog and stack file that uses numeric keys.
Assisted-by: Sisyphus:claude-sonnet-4-6 opencode
Describe the Bug
Since v1.212.0 (PR #2201 — native deep merge replacing mergo), a nested map whose parent key is an unquoted integer in YAML (e.g.
eni.1) is replaced instead of recursively deep-merged when a stack overrides a subset of its keys.With mergo (≤ v1.211.0) the two maps were deep-merged — keys present only in the catalog were preserved alongside keys present only in the stack override. With the native implementation the catalog keys are silently dropped.
The partial regression fix in PR #2248 addressed
list → maptype mismatches, but did not address the case where both sides are maps but the parent key is parsed as a YAML integer.Expected Behavior
Given a catalog that defines:
And a stack that adds keys to the same
eni.1entry:atmos describe component ec2-custom-nss-web -s ue1-pci-itshould produce a mergedeni.1:Actual result (v1.212.0):
descriptionandegress,tcp,443are dropped — the stack'seni.1entirely replaces the catalog'seni.1:Steps to Reproduce
config.1in the output.v1.211.0:
config.1containsfrom_catalog: true,from_stack: true,shared_key: stack_value(deep merge).v1.212.0:
config.1contains onlyfrom_stack: true,shared_key: stack_value—from_catalogis absent (replace).Workaround: Quoting the key (
"1":) in both files forces a string key and the native merge handles it correctly, asdeepMergeNativeonly operates onmap[string]any.Screenshots
N/A — see Steps to Reproduce output above.
Environment
atmos describe component, no Terraform invocation needed)Additional Context
Root cause hypothesis:
Go's
yaml.v3parses an unquoted integer key (1:) asint(1), notstring("1"). Before merging, Atmos normalizes maps tomap[string]any. If this normalization runs correctly, the key becomes"1"and the native merge should recurse into it. However, if any step in the pipeline between YAML unmarshaling anddeepMergeNativeskips normalization, the map may arrive asmap[interface{}]anyormap[int]any. The native merge's type switch only matchesmap[string]any; an unrecognized map type falls through as a scalar leaf and is replaced rather than recursed into.Mergo used reflection and could merge any concrete map type, masking this normalization gap.
Related PRs:
list → maptype-mismatch guards, but not integer-key map normalizationWorkaround available: Quote all integer YAML keys (
"1":instead of1:). This is safe but requires touching every catalog and stack file that uses numeric keys.Assisted-by: Sisyphus:claude-sonnet-4-6 opencode