{"id":"d0284b89f20b579e","repo":"gohugoio/hugo","slug":"circular-call-stack-detected-in-partial-q","errorCode":null,"errorMessage":"circular call stack detected in partial %q","messagePattern":"circular call stack detected in partial %q","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"tpl/partials/partials.go","lineNumber":223,"sourceCode":"func (ns *Namespace) IncludeCached(ctx context.Context, name string, context any, variants ...any) (any, error) {\n\tstart := time.Now()\n\tkey := partialCacheKey{\n\t\tName:     name,\n\t\tVariants: variants,\n\t}\n\tkeyString := key.Key()\n\n\tdepsManagerIn := tpl.Context.GetDependencyManagerInCurrentScope(ctx)\n\tti, err := ns.lookup(name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif parent := tpl.Context.CurrentTemplate.Get(ctx); parent != nil {\n\t\tfor parent != nil {\n\t\t\tif parent.CurrentTemplateInfoOps == ti && parent.Key == keyString {\n\t\t\t\t// This will deadlock if we continue.\n\t\t\t\treturn nil, fmt.Errorf(\"circular call stack detected in partial %q\", ti.Filename())\n\t\t\t}\n\t\t\tparent = parent.Parent\n\t\t}\n\t}\n\n\tr, found, err := ns.cachedPartials.cache.GetOrCreate(keyString, func(string) (includeResult, error) {\n\t\tvar depsManagerShared identity.Manager\n\t\tif ns.deps.Conf.Watching() {\n\t\t\t// We need to create a shared dependency manager to pass downwards\n\t\t\t// and add those same dependencies to any cached invocation of this partial.\n\t\t\tdepsManagerShared = identity.NewManager()\n\t\t\tctx = tpl.Context.DependencyManagerScopedProvider.Set(ctx, depsManagerShared.(identity.DependencyManagerScopedProvider))\n\t\t}\n\t\t// Mark the ctx so templates.Defer can reject being called from a cached body.\n\t\tctx = tpl.Context.IsInPartialCached.Set(ctx, true)\n\t\tr := ns.doInclude(ctx, keyString, ti, context)\n\t\tif ns.deps.Conf.Watching() {\n\t\t\tr.mangager = depsManagerShared","sourceCodeStart":205,"sourceCodeEnd":241,"githubUrl":"https://github.com/gohugoio/hugo/blob/8a468df065a75c1c7cf9f6850f32148746590ea5/tpl/partials/partials.go#L205-L241","documentation":"Raised by `partialCached` (tpl/partials/partials.go:223) when the partial being invoked, with the same cache-variant key, is already on the current template call stack. Because `partialCached` uses a get-or-create cache keyed by name+variants, a self-recursive cached call would block waiting on its own in-flight cache entry, so Hugo detects the cycle by walking the parent template chain and fails instead of deadlocking.","triggerScenarios":"A partial calling itself via `partialCached` (directly or through intermediaries) with identical variant arguments, e.g. `layouts/_partials/menu.html` containing `{{ partialCached \"menu.html\" . }}` for nested menu levels without a distinguishing variant.","commonSituations":"Recursive menu/tree rendering converted from `partial` to `partialCached` for performance; two partials that cachedly include each other; recursion where the variant key doesn't change per level so every recursion maps to the same cache entry.","solutions":["Use plain `partial` instead of `partialCached` for recursive partials.","If caching is needed, pass a variant argument that differs at each recursion level (e.g. the section path or depth): `{{ partialCached \"menu.html\" . .Depth }}`.","Break the cycle by splitting the partial: a cached outer wrapper that calls an uncached recursive inner partial."],"exampleFix":"<!-- before: menu.html -->\n{{ partialCached \"menu.html\" .Child }}\n<!-- after -->\n{{ partial \"menu.html\" .Child }}","handlingStrategy":"validation","validationCode":"{{/* break recursion with an explicit depth counter passed in context */}}\n{{ $depth := default 0 .depth }}\n{{ if lt $depth 10 }}\n  {{ partial \"tree.html\" (dict \"node\" .node \"depth\" (add $depth 1)) }}\n{{ end }}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Give recursive partials an explicit termination condition (depth counter or leaf check)","Vary the context argument on each recursive call — identical name+context is detected as a cycle","Prefer partialCached only for non-recursive partials; caching does not fix cycles","Diagram partial include chains when A includes B includes A indirectly"],"tags":["hugo","templates","partials","recursion","caching"],"analyzedSha":"8a468df065a75c1c7cf9f6850f32148746590ea5","analyzedAt":"2026-07-31T21:23:07.045Z","schemaVersion":2}