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.

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)

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.