nikivdev/code · error

cycle detected while loading config includes

Error message

cycle detected while loading config includes: {}

What it means

Thrown while loading a config file and its `include` chain when a file about to be loaded has already been visited in the current load (detected via canonicalized paths). This prevents infinite recursion, e.g. a config that includes itself directly or through a loop of two or more files. The message names the path where the cycle was detected.

Solutions

  1. Trace the include chain printed in the message and remove the include that closes the loop
  2. Check for symlinks: `readlink -f <path>` to see the canonical target and de-duplicate includes
  3. Ensure each config includes at most one chain without returning to an ancestor
  4. Restructure shared settings into a base config included once by each side

Example fix

# before (cycle)
# config.toml
include = ["config.toml"]
# after
include = []
Defensive patterns

Strategy: validation

Validate before calling

// detect an include cycle before loading
fn would_cycle(path: &Path, visited: &HashSet<PathBuf>) -> Result<()> {
    let canonical = path.canonicalize()?;
    if visited.contains(&canonical) {
        bail!("config include cycle at {}", path.display());
    }
    Ok(())
}

Try / catch

match load_config_with_includes(&path) {
    Err(e) if e.to_string().contains("cycle detected while loading config includes") => {
        eprintln!("{e:#}");
        eprintln!("Fix your config: remove the include that points back to an already-loaded file (check symlinks with readlink -f).");
        Config::default()
    }
    other => other?,
}

Prevention

When it happens

Trigger: Config A includes config B and B includes A; a config includes itself; or two paths that canonicalize to the same file (symlink/./.. tricks) are included repeatedly.

Common situations: Hand-edited include chains that loop, symlinked config files (e.g. dotfile managers linking config into itself), copying a template that includes its own path, or directory+file includes resolving to the same canonical file.

Related errors


AI-assisted analysis of nikivdev/code@a747e741ae (2026-09-01). Data as JSON: /api/errors/ada9a0ff5995462a. Report an issue: GitHub.

Appendix: source

Thrown at src/config.rs:2384

        if secrets_path.exists() {
            if let Ok(secrets) = load_secrets(&secrets_path) {
                merge_secrets(cfg, secrets);
                tracing::debug!(path = %secrets_path.display(), "loaded secrets file");
            }
        }
    }
}

fn load_with_includes(
    path: &Path,
    visited: &mut Vec<PathBuf>,
    watched_paths: &mut Vec<PathBuf>,
) -> Result<Config> {
    let canonical = path
        .canonicalize()
        .with_context(|| format!("failed to resolve path {}", path.display()))?;
    if visited.contains(&canonical) {
        anyhow::bail!(
            "cycle detected while loading config includes: {}",
            path.display()
        );
    }
    visited.push(canonical.clone());
    watched_paths.push(canonical.clone());

    let contents = fs::read_to_string(&canonical)
        .with_context(|| format!("failed to read flow config at {}", path.display()))?;
    let mut cfg: Config = match toml::from_str(&contents) {
        Ok(cfg) => cfg,
        Err(err) => {
            let fix = fixup::fix_toml_content(&contents);
            if fix.fixes_applied.is_empty() {
                return Err(err)
                    .with_context(|| format!("failed to parse flow config at {}", path.display()));
            }
            let fixed = fixup::apply_fixes_to_content(&contents, &fix.fixes_applied);

View on GitHub (pinned to a747e741ae)