{"record":{"id":"438e45c3ee237333","repo":"siyuan-note/siyuan","slug":"recursive-template-call-s-is-not-supported","errorCode":null,"errorMessage":"recursive template call [%s] is not supported","messagePattern":"recursive template call \\[(.+?)\\] is not supported","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/template_doc_tree_render.go","lineNumber":112,"sourceCode":"\tif nil != err {\n\t\treturn nil, fmt.Errorf(Conf.Language(44), err.Error())\n\t}\n\tif err = validateTemplateCallGraph(childTemplate, childTemplate.Name()); nil != err {\n\t\treturn nil, err\n\t}\n\tif err = childTemplate.Execute(buf, dataModel); nil != err {\n\t\treturn nil, fmt.Errorf(Conf.Language(44), err.Error())\n\t}\n\treturn buf.Bytes(), nil\n}\n\nfunc validateTemplateCallGraph(root *template.Template, start string) error {\n\tvisiting := map[string]bool{}\n\tdepths := map[string]int{}\n\tvar visit func(string) (int, error)\n\tvisit = func(name string) (int, error) {\n\t\tif visiting[name] {\n\t\t\treturn 0, fmt.Errorf(\"recursive template call [%s] is not supported\", name)\n\t\t}\n\t\tif depth, ok := depths[name]; ok {\n\t\t\treturn depth, nil\n\t\t}\n\t\ttmpl := root.Lookup(name)\n\t\tif nil == tmpl || nil == tmpl.Tree || nil == tmpl.Tree.Root {\n\t\t\treturn 0, fmt.Errorf(\"template definition [%s] not found\", name)\n\t\t}\n\t\tvisiting[name] = true\n\t\tdepth := 1\n\t\tfor _, called := range collectCalledTemplateNames(tmpl.Tree.Root) {\n\t\t\tcalledDepth, err := visit(called)\n\t\t\tif nil != err {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t\tif depth < calledDepth+1 {\n\t\t\t\tdepth = calledDepth + 1\n\t\t\t}","sourceCodeStart":94,"sourceCodeEnd":130,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/template_doc_tree_render.go#L94-L130","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before: mutual recursion\n{{define \"a\"}}{{template \"b\"}}{{end}}\n{{define \"b\"}}{{template \"a\"}}{{end}}\n// after: extract shared leaf\n{{define \"a\"}}{{template \"leaf\"}}{{end}}\n{{define \"b\"}}{{template \"leaf\"}}{{end}}\n{{define \"leaf\"}}shared content{{end}}","handlingStrategy":"validation","validationCode":"func checkNoCycles(defines map[string][]string) error {\n\tvisiting := map[string]bool{}\n\tvar visit func(string) error\n\tvisit = func(n string) error {\n\t\tif visiting[n] { return fmt.Errorf(\"cycle at %s\", n) }\n\t\tvisiting[n] = true\n\t\tfor _, c := range defines[n] { if err := visit(c); err != nil { return err } }\n\t\tvisiting[n] = false\n\t\treturn nil\n\t}\n\tfor n := range defines { if err := visit(n); err != nil { return err } }\n\treturn nil\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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"],"tags":["go","template","recursion","call-graph"],"backgroundTag":"template-recursion-detected","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}