siyuan-note/siyuan · error
recursive template call [%s] is not supported
Error message
recursive template call [%s] is not supported
What it means
validateTemplateCallGraph walks the static {{template ...}} call graph of a parsed template set and rejects any cycle: if visit() reaches a template definition that is already on the current visiting stack, the set is recursive and rendering is refused with this error. Go's text/template would infinitely recurse at execution time, so SiYuan detects it statically before executing.
Source
Thrown at kernel/model/template_doc_tree_render.go:112
if nil != err {
return nil, fmt.Errorf(Conf.Language(44), err.Error())
}
if err = validateTemplateCallGraph(childTemplate, childTemplate.Name()); nil != err {
return nil, err
}
if err = childTemplate.Execute(buf, dataModel); nil != err {
return nil, fmt.Errorf(Conf.Language(44), err.Error())
}
return buf.Bytes(), nil
}
func validateTemplateCallGraph(root *template.Template, start string) error {
visiting := map[string]bool{}
depths := map[string]int{}
var visit func(string) (int, error)
visit = func(name string) (int, error) {
if visiting[name] {
return 0, fmt.Errorf("recursive template call [%s] is not supported", name)
}
if depth, ok := depths[name]; ok {
return depth, nil
}
tmpl := root.Lookup(name)
if nil == tmpl || nil == tmpl.Tree || nil == tmpl.Tree.Root {
return 0, fmt.Errorf("template definition [%s] not found", name)
}
visiting[name] = true
depth := 1
for _, called := range collectCalledTemplateNames(tmpl.Tree.Root) {
calledDepth, err := visit(called)
if nil != err {
return 0, err
}
if depth < calledDepth+1 {
depth = calledDepth + 1
}View on GitHub (pinned to 8641553a1f)
Solutions
- Break the recursion: remove the {{template}} call that closes the cycle or gate it behind a condition that cannot recurse
- Restructure shared content into a leaf define that both templates call instead of calling each other
- Duplicate the needed content into each template if mutual inclusion is truly required
- Trace the cycle by following {{template "..."}} calls from the named template in the error
Example fix
// before: mutual recursion
{{define "a"}}{{template "b"}}{{end}}
{{define "b"}}{{template "a"}}{{end}}
// after: extract shared leaf
{{define "a"}}{{template "leaf"}}{{end}}
{{define "b"}}{{template "leaf"}}{{end}}
{{define "leaf"}}shared content{{end}} Defensive patterns
Strategy: validation
Validate before calling
func checkNoCycles(defines map[string][]string) error {
visiting := map[string]bool{}
var visit func(string) error
visit = func(n string) error {
if visiting[n] { return fmt.Errorf("cycle at %s", n) }
visiting[n] = true
for _, c := range defines[n] { if err := visit(c); err != nil { return err } }
visiting[n] = false
return nil
}
for n := range defines { if err := visit(n); err != nil { return err } }
return nil
} Prevention
- Design shared content as leaf defines that never include callers
- Draw the {{template}} include graph when composing multiple template packages
- Never 'wrap' a template with a template that also gets included by it
When it happens
Trigger: A define (or external child template) contains {{template "a"}} where a eventually calls back into itself, directly or through a chain of defines, e.g. define A calls B and B calls A.
Common situations: Refactoring shared headers/footers into defines and accidentally re-including a parent; copy-pasting template includes creating mutual recursion between two child files; generics-style 'wrapper' patterns that don't terminate.
Related errors
- attribute view [%s] view [%s] not found
- attribute view [%s] has no available view: %w
- attribute view [%s] visible view [%s] not found
- attribute view [%s] has no available visible view
- template source is too large
AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11).
Data as JSON: /api/errors/438e45c3ee237333.
Report an issue: GitHub.