microsoft/aspire · error
aspire: deep update recursion limit of '32' exceeded
Error message
aspire: deep update recursion limit of '32' exceeded
What it means
The Go code generation base template's merge function performs a recursive deep-merge of nested map[string]any values and enforces a hard recursion depth of 32. When the merged structures nest deeper than 32 levels it panics to prevent unbounded recursion and stack exhaustion. This is an internal invariant guard for malformed or pathologically nested merge inputs.
Solutions
- Reduce the nesting depth of the maps being merged (flatten or restructure the data model).
- Check for accidental cyclic or self-nesting map construction feeding deepUpdate.
- Pre-merge shallowly: merge leaf sections individually instead of one whole deep tree.
- Restructure the generator rather than raising the limit, since depthLimit is a compiled constant.
Example fix
// before: single merge of an arbitrarily deep tree
result := deepUpdate(base, userOverrides)
// after: merge per top-level section to keep depth bounded
for section, override := range userOverrides {
deepUpdate(base[section].(map[string]any), override.(map[string]any))
} Defensive patterns
Strategy: validation
Validate before calling
func mapDepth(m map[string]any) int {
max := 1
for _, v := range m {
if sub, ok := v.(map[string]any); ok {
if d := mapDepth(sub) + 1; d > max {
max = d
}
}
}
return max
}
// call deepUpdate only if mapDepth(dst) < 30 && mapDepth(src) < 30 Type guard
func isMergeSafe(dst, src map[string]any) bool {
return mapDepth(dst) < 30 && mapDepth(src) < 30
} Try / catch
defer func() {
if r := recover(); r != nil {
if s, ok := r.(string); ok && strings.Contains(s, "recursion limit") {
err = fmt.Errorf("merge input nested too deeply: %v", s)
} else {
panic(r)
}
}
}() Prevention
- Flatten deeply nested config models before merging.
- Avoid building self-referential map structures.
- Merge section-by-section rather than whole deep trees.
- Log nesting depth in debug mode when constructing generated trees.
When it happens
Trigger: Calling deepUpdate/merge with two map[string]any values whose nested sub-maps chain more than 32 levels deep (the depth parameter exceeds the depthLimit constant of 32).
Common situations: Generated output from deeply nested cloud resource definitions (deeply nested ARM/Bicep JSON) being merged; accidental self-referential or cyclic map construction; many levels of config/environment overrides stacked programmatically into one tree.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- argument ' ' passed to capability ' ' contains a circular…
- aspire: build returned unexpected type %T
- aspire: returned unexpected type %T
- aspire: createBuilder returned unexpected type %T
- aspire: : parameter must be one of [ ], got %T
AI-assisted analysis of microsoft/aspire@25830f84bd (2026-09-16).
Data as JSON: /api/errors/80a04ed370c2ba1d.
Report an issue: GitHub.
Appendix: source
Thrown at src/Aspire.Hosting.CodeGeneration.Go/Resources/base.go:890
var result T
if reflect.TypeOf(result).Kind() == reflect.Ptr {
result = reflect.New(reflect.TypeOf(result).Elem()).Interface().(T)
}
if err := json.Unmarshal(bytes, &result); err != nil {
panic(fmt.Sprintf("aspire: merge error: %v", err))
}
return result
default:
return src
}
}
func merge(dst, src map[string]any, depth int) map[string]any {
const depthLimit = 32
if depth > depthLimit {
panic("aspire: deep update recursion limit of '32' exceeded")
}
for key, srcVal := range src {
if dstVal, ok := dst[key]; ok {
srcSub, srcOk := toMap(srcVal)
dstSub, dstOk := toMap(dstVal)
if srcOk && dstOk {
dst[key] = merge(dstSub, srcSub, depth+1)
continue
}
}
dst[key] = srcVal
}
return dst
}
func toMap(i any) (map[string]any, bool) {View on GitHub (pinned to 25830f84bd)