nikivdev/code · error · anyhow::Error
cycle detected while loading config includes: {}
Error message
cycle detected while loading config includes: {} What it means
Raised by `load_discovery_config` (src/discover.rs:315) while recursively loading config includes: it canonicalizes each path and, if that canonical path is already in the `visited` list, a cycle exists (e.g. a includes b which includes a). The cycle would otherwise cause infinite recursion, so loading aborts and reports the offending path.
Source
Thrown at src/discover.rs:315
.cloned()
.unwrap_or_else(|| "root".to_string());
(primary, aliases)
}
fn push_watched_path(paths: &mut Vec<PathBuf>, path: &Path) {
if !paths.iter().any(|existing| existing == path) {
paths.push(path.to_path_buf());
}
}
fn load_discovery_config(
path: &Path,
visited: &mut Vec<PathBuf>,
watched_paths: &mut Vec<PathBuf>,
) -> Result<LoadedDiscoveryConfig> {
let canonical = path.canonicalize()?;
if visited.contains(&canonical) {
anyhow::bail!(
"cycle detected while loading config includes: {}",
path.display()
);
}
visited.push(canonical.clone());
push_watched_path(watched_paths, &canonical);
let contents = fs::read_to_string(&canonical)?;
let mut cfg = parse_discovery_config(&canonical, &contents)?;
let mut project_name = cfg.project_name.take();
let mut tasks = cfg.tasks;
let mut task_resolution = cfg.task_resolution.take();
for include in cfg.command_files {
let include_path = config::resolve_include_path(&canonical, &include.path);
let included = load_discovery_config(&include_path, visited, watched_paths)?;
if project_name.is_none() {View on GitHub (pinned to a747e741ae)
Solutions
- Trace the include chain and remove the include that points back to an already-included file
- Break the cycle by extracting shared settings into a common file included once
- Replace symlink loops with a single canonical shared config path
- The message names the path where the cycle was detected — start pruning there
Example fix
// before (a.toml) include = ["b.toml"] # b.toml include = ["a.toml"] // after # a.toml include = ["b.toml"] # b.toml — remove the back-reference include = []
Defensive patterns
Strategy: validation
Validate before calling
fn has_include_cycle(entry: &Path) -> Result<bool> {
let mut seen = std::collections::HashSet::new();
let mut stack = vec![entry.canonicalize()?];
while let Some(p) = stack.pop() {
if !seen.insert(p.clone()) { return Ok(true); }
for inc in parse_includes(&p)? {
stack.push(inc.canonicalize()?);
}
}
Ok(false)
}
// call before loading; skip load if it returns Ok(true) Type guard
fn acyclic(entry: &Path) -> bool { has_include_cycle(entry).unwrap_or(false) } Try / catch
match load_discovery_config(path, &mut visited, &mut watched) {
Err(e) if e.to_string().contains("cycle detected while loading config includes") => {
eprintln!("fix the include chain at {} and retry", e.to_string());
}
other => other?,
} Prevention
- Keep a single shared config included once, never re-included by children
- Avoid symlink loops in config directories
- After merging config changes, grep includes for back-references
- Track visited includes when generating configs programmatically
When it happens
Trigger: A discovery config include chain that revisits any file: `a.toml` includes `b.toml` which includes `a.toml`, a self-include (`a.toml` includes `a.toml`), or symlinked directories whose canonicalized paths resolve back into an already-visited file.
Common situations: Copying an include block into a nested config without removing it from the parent, symlinking a shared config directory back onto itself, or merge tooling duplicating includes.
Related errors
- cycle detected while loading config includes: {}
- No profile selected.
- expected [{}] to be a table in global flow config
- empty resolver command for {}
- FLOW_CODEX_MAPLE_LOCAL_ENDPOINT and FLOW_CODEX_MAPLE_LOCAL_I
AI-assisted analysis of nikivdev/code@a747e741ae (2026-09-01).
Data as JSON: /api/errors/5f4607b493c058d1.
Report an issue: GitHub.