gohugoio/hugo · critical

maximum template call stack size exceeded in %q

Error message

maximum template call stack size exceeded in %q

What it means

Hugo tracks template nesting depth per execution via `CurrentTemplateInfo.Level` in `TemplateStore.ExecuteWithContextAndKey`. When a template invocation chain exceeds 999 levels, Hugo aborts with this error naming the template file, since it almost certainly indicates infinite recursion between templates/partials rather than legitimate nesting.

Source

Thrown at tpl/tplimpl/templatestore.go:541

	templ := ti.Template

	parent := tpl.Context.CurrentTemplate.Get(ctx)
	var level int
	if parent != nil {
		level = parent.Level + 1
	}
	currentTi := &tpl.CurrentTemplateInfo{
		Parent:                 parent,
		Level:                  level,
		Key:                    key,
		CurrentTemplateInfoOps: ti,
	}

	ctx = tpl.Context.CurrentTemplate.Set(ctx, currentTi)

	const levelThreshold = 999
	if level > levelThreshold {
		return fmt.Errorf("maximum template call stack size exceeded in %q", ti.Filename())
	}

	if t.opts.Metrics != nil {
		defer t.opts.Metrics.MeasureSince(templ.Name(), time.Now())
	}

	execErr := t.storeSite.executer.ExecuteWithContext(ctx, ti, wr, data)
	if execErr != nil {
		return t.addFileContext(ti, "execute of template failed", execErr)
	}
	return nil
}

func (t *TemplateStore) GetFunc(name string) (reflect.Value, bool) {
	v, found := t.storeSite.execHelper.funcs[name]
	return v, found
}

View on GitHub ↗ (pinned to 8a468df065)

Solutions

  1. Open the template named in the error and trace its `partial`/`template` calls for a cycle back to itself; add a terminating condition (e.g. only recurse when `.Children` is non-empty).
  2. Pass a depth counter in the partial context (`dict "depth" (add .depth 1)`) and stop at a sane limit.
  3. Check render hooks (layouts/_markup/*) that call `.RenderString` or partials which re-enter the same hook.

Example fix

<!-- before: partials/menu.html -->
{{ range .entries }}{{ partial "menu.html" (dict "entries" .Children) }}{{ end }}
<!-- after -->
{{ range .entries }}{{ with .Children }}{{ partial "menu.html" (dict "entries" .) }}{{ end }}{{ end }}
Defensive patterns

Strategy: validation

Validate before calling

{{/* pass an explicit depth guard to recursive partials */}}
{{ $depth := .depth | default 0 }}
{{ if lt $depth 50 }}{{ partial "tree.html" (dict "node" .node "depth" (add $depth 1)) }}{{ end }}

Prevention

When it happens

Trigger: A partial that includes itself (directly or via a cycle: A includes B includes A) without a terminating condition; recursive `partial`/`template` calls on nested data structures (e.g. menu trees) whose recursion never bottoms out; a baseof/block cycle.

Common situations: Recursive menu or table-of-contents partials missing the base-case check (e.g. forgetting `{{ if .Children }}` before re-invoking); a partial renamed such that it accidentally calls itself instead of a differently-named helper; render hooks that render markdown which re-triggers the same hook.

Related errors


AI-assisted analysis of gohugoio/hugo@8a468df065 (2026-07-31). Data as JSON: /data/errors/59825a66bbef044d.json. Report an issue: GitHub ↗.