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

  1. Reduce the nesting depth of the maps being merged (flatten or restructure the data model).
  2. Check for accidental cyclic or self-nesting map construction feeding deepUpdate.
  3. Pre-merge shallowly: merge leaf sections individually instead of one whole deep tree.
  4. 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

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


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)