Hmbown/CodeWhale · error

config path must include a file name

Error message

config path must include a file name

What it means

normalize_config_file_path requires the path to name a file: after rejecting '..' components it checks path.file_name() and fails if there is none. Paths ending in '/', '.', '..', or a filesystem root do not identify a config file, so the library refuses them.

Solutions

  1. Pass the full path including the file name, e.g. "/home/user/.config/codewhale/config.toml"
  2. If you have a directory, append the expected file name before loading
  3. Check for trailing slashes or '.' placeholders in generated paths

Example fix

// before
load_config(PathBuf::from("/home/user/.config/codewhale/"))
// after
load_config(PathBuf::from("/home/user/.config/codewhale/config.toml"))
Defensive patterns

Strategy: validation

Validate before calling

fn names_a_file(p: &std::path::Path) -> bool {
    p.file_name().is_some()
}

Type guard

fn with_file_name(p: &std::path::Path, name: &str) -> std::path::PathBuf {
    if p.file_name().is_some() { p.to_path_buf() } else { p.join(name) }
}

Try / catch

match normalize_config_file_path(path) {
    Ok(p) => p,
    Err(e) if e.to_string().contains("must include a file name") => {
        eprintln!("point --config at the file, not a directory");
        std::process::exit(2);
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Passing "/" (root), "/etc/", ".", "..", or any path whose final component is a directory indicator to the config loader.

Common situations: Pointing the loader at a config directory instead of the file inside it, or a template placeholder like "/config/" left unsubstituted in a deployment manifest.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

Thrown at crates/config/src/lib.rs:6766

            toml::Value::Table(redacted)
        }
        _ if sensitive => toml::Value::String("********".to_string()),
        _ => value.clone(),
    }
}

fn normalize_config_file_path(path: PathBuf) -> Result<PathBuf> {
    if path.as_os_str().is_empty() {
        bail!("config path cannot be empty");
    }
    if path
        .components()
        .any(|component| matches!(component, Component::ParentDir))
    {
        bail!("config path cannot contain '..' components");
    }
    if path.file_name().is_none() {
        bail!("config path must include a file name");
    }
    let absolute = if path.is_absolute() {
        path
    } else {
        std::env::current_dir()
            .context("failed to resolve current directory for config path")?
            .join(path)
    };
    let file_name = absolute
        .file_name()
        .map(OsString::from)
        .context("config path must include a file name")?;
    let parent = absolute
        .parent()
        .context("config path must include a parent directory")?;
    let parent = match parent.canonicalize() {
        Ok(parent) => parent,
        Err(err) if err.kind() == std::io::ErrorKind::NotFound => parent.to_path_buf(),

View on GitHub (pinned to 73e0f67d83)