projectdiscovery/nuclei · error
circular include directive detected: %s
Error message
circular include directive detected: %s
What it means
The YAML preprocessor detected a circular include chain while expanding include directives. readIncludedFile tracks every file currently on the include stack by absolute path (includePathKey); if the file being included is already on the stack, expansion aborts with this error naming the offending include path.
Source
Thrown at pkg/utils/yaml/preprocess.go:125
// pad each line of file content with padBytes
includeFileContent = bytes.ReplaceAll(includeFileContent, []byte("\n"), padBytes)
// copy everything up to the directive (including its indentation), then
// the expanded content, and resume after the directive.
out.Write(data[lastEnd:matchStart])
out.Write(includeFileContent)
lastEnd = matchEnd
}
out.Write(data[lastEnd:])
return out.Bytes(), nil
}
func readIncludedFile(includeFileName string, includeStack map[string]struct{}, depth int) ([]byte, error) {
includePath := includePathKey(includeFileName)
if _, ok := includeStack[includePath]; ok {
return nil, fmt.Errorf("circular include directive detected: %s", includeFileName)
}
includeStack[includePath] = struct{}{}
defer delete(includeStack, includePath)
includeFileContent, err := os.ReadFile(includeFileName)
if err != nil {
return nil, err
}
// if it's yaml, tries to preprocess that too recursively
if stringsutil.HasSuffixAny(includeFileName, extensions.YAML) {
if depth >= maxIncludeDepth {
return nil, fmt.Errorf("include directive exceeded maximum include depth of %d", maxIncludeDepth)
}
includeFileContent, err = preProcess(includeFileContent, includeFileName, includeStack, depth+1)
if err != nil {
return nil, errView on GitHub (pinned to 265b3a3dec)
Solutions
- Map the include chain: the error names the file that closes the loop; walk includes from the root template to find the back-edge.
- Break the cycle by inlining the shared snippet or extracting the mutually-needed part into a third file that both A and B include one-way.
- Validate locally with the template-validate target or by loading the template through pkg/utils/yaml Preprocess before shipping.
- Never add an include of an ancestor file inside a snippet meant to be embedded.
Example fix
# before # a.yaml include: b.yaml # b.yaml include: a.yaml # -> circular include directive detected: a.yaml # after # common.yaml holds the shared lines # a.yaml include: common.yaml # b.yaml include: common.yaml
Defensive patterns
Strategy: validation
Validate before calling
// pre-validate an include tree before preprocessing
func detectCycle(root string, seen map[string]bool) error {
abs, _ := filepath.Abs(root)
if seen[abs] { return fmt.Errorf("circular include at %s", abs) }
seen[abs] = true
defer delete(seen, abs)
data, err := os.ReadFile(root)
if err != nil { return err }
for _, m := range includeRe.FindAllStringSubmatch(string(data), -1) {
if err := detectCycle(filepath.Join(filepath.Dir(root), m[1]), seen); err != nil { return err }
}
return nil
} Try / catch
content, err := yamlutil.Preprocess(raw, path)
if err != nil && strings.Contains(err.Error(), "circular include") {
// template defect: fail validation, never ship/retry
return fmt.Errorf("bad template %s: %w", path, err)
} Prevention
- Keep includes one-directional: snippets never include consumers.
- Run include-graph validation in template CI.
- The detector keys on absolute path — beware copies that hide, not fix, cycles.
When it happens
Trigger: Template A includes B and B includes A (directly or through a longer chain like A→B→C→A); a file including itself; two files including each other via different relative paths that resolve to the same absolute path.
Common situations: Refactoring nuclei template YAML files and accidentally creating mutual references; a shared 'common' file that was later taught to include one of its consumers; symlinks/duplicate paths making a diamond look like a cycle is NOT flagged (only true cycles are), so this almost always means a real loop.
Related errors
- no fuzz values specified
- include directive exceeded maximum include depth of %d
- validation failed for these fields
- both verbose and silent mode specified
- probe concurrency must be at least 1
AI-assisted analysis of projectdiscovery/nuclei@265b3a3dec (2026-08-15).
Data as JSON: /api/errors/ed4682bee4ce8d6e.
Report an issue: GitHub.