{"record":{"id":"28575ce703f807f7","repo":"caddyserver/caddy","slug":"virtual-request-cycle","errorCode":null,"errorMessage":"virtual request cycle","messagePattern":"virtual request cycle","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"modules/caddyhttp/templates/tplcontext.go","lineNumber":183,"sourceCode":"\n\treturn nil\n}\n\n// funcHTTPInclude returns the body of a virtual (lightweight) request\n// to the given URI on the same server. Note that included bodies\n// are NOT escaped, so you should only include trusted resources.\n// If it is not trusted, be sure to use escaping functions yourself.\nfunc (c TemplateContext) funcHTTPInclude(uri string) (string, error) {\n\t// prevent virtual request loops by counting how many levels\n\t// deep we are; and if we get too deep, return an error\n\trecursionCount := 1\n\tif numStr := c.Req.Header.Get(recursionPreventionHeader); numStr != \"\" {\n\t\tnum, err := strconv.Atoi(numStr)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"parsing %s: %v\", recursionPreventionHeader, err)\n\t\t}\n\t\tif num >= 3 {\n\t\t\treturn \"\", fmt.Errorf(\"virtual request cycle\")\n\t\t}\n\t\trecursionCount = num + 1\n\t}\n\n\tbuf := bufPool.Get().(*bytes.Buffer)\n\tbuf.Reset()\n\tdefer bufPool.Put(buf)\n\n\tvirtReq, err := http.NewRequest(\"GET\", uri, nil)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tvirtReq.Host = c.Req.Host\n\tvirtReq.RemoteAddr = \"127.0.0.1:10000\" // https://github.com/caddyserver/caddy/issues/5835\n\tvirtReq.Header = c.Req.Header.Clone()\n\tvirtReq.Header.Set(\"Accept-Encoding\", \"identity\") // https://github.com/caddyserver/caddy/issues/4352\n\tvirtReq.Trailer = c.Req.Trailer.Clone()\n\tvirtReq.Header.Set(recursionPreventionHeader, strconv.Itoa(recursionCount))","sourceCodeStart":165,"sourceCodeEnd":201,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/modules/caddyhttp/templates/tplcontext.go#L165-L201","documentation":"Returned by the templates module's httpInclude function when the recursion-prevention header indicates the virtual request is already at nesting depth 3 or greater. Caddy increments this header on each nested virtual include to bound recursion.","triggerScenarios":"A template that calls {{ httpInclude \"/x\" }} where /x itself renders a template that httpIncludes back into /x (a cycle), or a legitimate include chain deeper than 3 levels. The header value reaching >= 3 triggers the error.","commonSituations":"Two templates including each other (A includes B, B includes A), a template including its own URL, or a shared header partial fetched via httpInclude that indirectly includes itself.","solutions":["Break the cycle: use {{ include \"partial\" }} (file include) instead of httpInclude for shared partials","Trace the include chain (each hop logs the URI) and remove the edge that loops back","Flatten deep chains to fewer than 3 levels of httpInclude","Ensure error pages that use templates do not httpInclude a URL that errors and re-enters the error handler"],"exampleFix":"<!-- before -->\n<!-- a.html -->\n{{ httpInclude \"/b\" }}\n<!-- b.html -->\n{{ httpInclude \"/a\" }}\n\n<!-- after -->\n<!-- a.html -->\n{{ include \"/b.html\" }}\n<!-- b.html -->\n(no include back; render content directly)","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":["Prefer file includes ({{ include }}) over httpInclude for shared partials","Document the include graph for each site to avoid cycles","Keep httpInclude chains under 3 levels by design"],"tags":["caddy","templates","recursion","http-include"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}