{"record":{"id":"1c0f34eca7df40a8","repo":"Hmbown/CodeWhale","slug":"config-path-must-include-a-file-name","errorCode":null,"errorMessage":"config path must include a file name","messagePattern":"config path must include a file name","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/config/src/lib.rs","lineNumber":6622,"sourceCode":"            toml::Value::Table(redacted)\n        }\n        _ if sensitive => toml::Value::String(\"********\".to_string()),\n        _ => value.clone(),\n    }\n}\n\nfn normalize_config_file_path(path: PathBuf) -> Result<PathBuf> {\n    if path.as_os_str().is_empty() {\n        bail!(\"config path cannot be empty\");\n    }\n    if path\n        .components()\n        .any(|component| matches!(component, Component::ParentDir))\n    {\n        bail!(\"config path cannot contain '..' components\");\n    }\n    if path.file_name().is_none() {\n        bail!(\"config path must include a file name\");\n    }\n    let absolute = if path.is_absolute() {\n        path\n    } else {\n        std::env::current_dir()\n            .context(\"failed to resolve current directory for config path\")?\n            .join(path)\n    };\n    let file_name = absolute\n        .file_name()\n        .map(OsString::from)\n        .context(\"config path must include a file name\")?;\n    let parent = absolute\n        .parent()\n        .context(\"config path must include a parent directory\")?;\n    let parent = match parent.canonicalize() {\n        Ok(parent) => parent,\n        Err(err) if err.kind() == std::io::ErrorKind::NotFound => parent.to_path_buf(),","sourceCodeStart":6604,"sourceCodeEnd":6640,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/config/src/lib.rs#L6604-L6640","documentation":"Thrown by normalize_config_file_path when path.file_name() is None — the path ends in a separator, is the filesystem root '/', or a Windows drive prefix like 'C:'. The normalizer requires the config path to designate a file, and such paths can only designate a directory (or nothing).","triggerScenarios":"`--config /` or `--config ~/.config/codewhale/` (trailing slash on a directory); passing the config directory when the file path was intended.","commonSituations":"Scripts that join a directory variable and forget the file name; users pointing at the folder that contains codewhale.toml rather than the file; a path template whose '/{file}' part was dropped.","solutions":["Append the file name: ~/.config/codewhale/codewhale.toml instead of ~/.config/codewhale/","Remove the trailing slash if you meant a file in the current directory (config.toml not config.toml/)","In code, build the path with join(\"codewhale.toml\") on the directory rather than passing the directory itself"],"exampleFix":"# before\ncodewhale --config ~/.config/codewhale/\n\n# after\ncodewhale --config ~/.config/codewhale/codewhale.toml","handlingStrategy":"validation","validationCode":"let p = Path::new(&flag);\nanyhow::ensure!(!p.as_os_str().is_empty(), \"config path required\");\nanyhow::ensure!(p.file_name().is_some(), \"config path must name a file, not a directory\");\n// safe to normalize now","typeGuard":"fn names_a_file(p: &std::path::Path) -> bool {\n    !p.as_os_str().is_empty() && p.file_name().is_some()\n}","tryCatchPattern":null,"preventionTips":["Always build config paths as dir.join(\"codewhale.toml\"), never pass the directory alone","Strip trailing separators from user input before treating it as a file path"],"tags":["rust","config","path","validation"],"backgroundTag":"invalid-file-path","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}