Hmbown/CodeWhale · error

agent profile path is not a directory

Error message

agent profile path {} is not a directory

What it means

agent_profile_paths validates that the agent profiles directory exists and is actually a directory before reading entries. If the path exists but is not a directory (e.g. a regular file or symlink to a file), the loader bails with this message rather than returning an IO error.

Solutions

  1. Fix the configured path to point at the profiles directory, not a file inside it
  2. Verify with `ls -la <path>` that the target is a directory (`d` in permissions)
  3. If you meant to load one profile file, place it in a directory and point config at the directory
  4. Recreate the directory if a tool replaced it with a file

Example fix

// before
agent_profiles = "/home/me/.codewhale/profiles/researcher.toml"

// after
agent_profiles = "/home/me/.codewhale/profiles"
Defensive patterns

Strategy: validation

Validate before calling

let meta = std::fs::metadata(path)?;
if !meta.is_dir() {
    eprintln!("{} is not a directory", path.display());
}

Type guard

fn is_existing_dir(p: &Path) -> bool {
    p.is_dir()
}

Try / catch

match result {
    Err(e) if e.to_string().contains("is not a directory") => {
        eprintln!("check the agent_profiles path in config — it must be a directory");
    }
    other => other?,
}

Prevention

When it happens

Trigger: Passing a file path (or a symlink to a file) as the agent profiles directory to load_agent_profiles_from_dir_tolerant / load_agent_profile_identities_from_dir / load_agent_profiles_from_dir; a path that exists via dir.exists() but fails dir.is_dir().

Common situations: Config pointing `agent_profiles` at a single TOML file instead of its containing directory; a miswritten path in config (missing a directory component); the directory being replaced by a file by a sync/backup tool.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/56ea8d0247701837. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/fleet/profile.rs:360

    let dir = dir.as_ref();
    let mut profiles = Vec::new();
    let mut seen = BTreeSet::new();
    for path in agent_profile_paths(dir)? {
        let profile = load_agent_profile_file(&path)?;
        if !seen.insert(profile.id.to_ascii_lowercase()) {
            bail!("duplicate agent profile id {}", profile.id);
        }
        profiles.push(profile);
    }
    Ok(profiles)
}

fn agent_profile_paths(dir: &Path) -> Result<Vec<PathBuf>> {
    if !dir.exists() {
        return Ok(Vec::new());
    }
    if !dir.is_dir() {
        bail!("agent profile path {} is not a directory", dir.display());
    }

    let mut paths = std::fs::read_dir(dir)
        .with_context(|| format!("reading agent profile dir {}", dir.display()))?
        .collect::<std::io::Result<Vec<_>>>()
        .with_context(|| format!("reading agent profile entries in {}", dir.display()))?
        .into_iter()
        .map(|entry| entry.path())
        .filter(|path| path.extension().and_then(|value| value.to_str()) == Some("toml"))
        .collect::<Vec<_>>();
    paths.sort();
    Ok(paths)
}

fn load_agent_profile_identity_file(path: &Path) -> Result<AgentProfileIdentity> {
    let raw = std::fs::read_to_string(path)
        .with_context(|| format!("reading agent profile identity {}", path.display()))?;
    let parsed: AgentProfileIdentityToml = toml::from_str(&raw)

View on GitHub (pinned to 73e0f67d83)