{"record":{"id":"8d4b39db4339edb3","repo":"caddyserver/caddy","slug":"a-cycle-of-imports-exists-between-s-and-s","errorCode":null,"errorMessage":"a cycle of imports exists between %s and %s","messagePattern":"a cycle of imports exists between (.+?) and (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"caddyconfig/caddyfile/importgraph.go","lineNumber":61,"sourceCode":"}\n\nfunc (i *importGraph) removeNode(name string) {\n\tdelete(i.nodes, name)\n}\n\nfunc (i *importGraph) removeNodes(names []string) {\n\tfor _, name := range names {\n\t\ti.removeNode(name)\n\t}\n}\n\nfunc (i *importGraph) addEdge(from, to string) error {\n\tif !i.exists(from) || !i.exists(to) {\n\t\treturn fmt.Errorf(\"one of the nodes does not exist\")\n\t}\n\n\tif i.willCycle(to, from) {\n\t\treturn fmt.Errorf(\"a cycle of imports exists between %s and %s\", from, to)\n\t}\n\n\tif i.areConnected(from, to) {\n\t\t// if connected, there's nothing to do\n\t\treturn nil\n\t}\n\n\tif i.nodes == nil {\n\t\ti.nodes = make(map[string]struct{})\n\t}\n\tif i.edges == nil {\n\t\ti.edges = make(adjacency)\n\t}\n\n\ti.edges[from] = append(i.edges[from], to)\n\treturn nil\n}\n","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/caddyconfig/caddyfile/importgraph.go#L43-L79","documentation":"The import graph detects that adding an edge from -> to would create a cycle: to (directly or transitively) already imports from, so making 'from' import 'to' would recurse forever. Caddy refuses the edge instead of looping.","triggerScenarios":"Two or more files/snippets import each other, directly or through intermediaries: A imports B, then B (or a file B imported) imports A. Detected in importgraph.addEdge during Caddyfile adaptation.","commonSituations":"Refactoring shared snippets where a common file grows to import a site file that imports it back; mutual imports between 'defaults' and 'overrides' snippets. The message names the two nodes that would close the cycle.","solutions":["Break the cycle: move the shared directives both files need into a third snippet that neither imports the other","Trace the chain between the two named files to find the smallest cycle","Use parameterized snippets (import with args) instead of files importing each other for reuse","Lint imports with `caddy adapt` after each refactor to catch cycles early"],"exampleFix":"# before: a.caddy does 'import b.caddy', b.caddy does 'import a.caddy'\n# after: extract shared parts into common.caddy; a.caddy and b.caddy each 'import common.caddy'","handlingStrategy":"validation","validationCode":"// pre-flight: build the import graph yourself and reject cycles before adapting\nseen := map[string]bool{}\nvar visit func(path string, stack []string) error\nvisit = func(path string, stack []string) error {\n    if seen[path] { return nil }\n    for _, s := range stack { if s == path { return fmt.Errorf(\"cycle via %s\", path) } }\n    // recurse into import statements found in path...\n    return nil\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Make snippet files leaf nodes: snippets import nothing","Refactor shared config into a single common snippet","Adapt configs in CI to catch cycles introduced by refactors"],"tags":["caddyfile","imports","cycle","config"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}