{"record":{"id":"6c9f8eed2a923a78","repo":"Hmbown/CodeWhale","slug":"external-credential-path-must-name-a-file","errorCode":null,"errorMessage":"external credential path must name a file","messagePattern":"external credential path must name a file","errorType":"validation","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/external_credentials.rs","lineNumber":217,"sourceCode":"        let flags = if leaf {\n            libc::O_RDONLY | libc::O_CLOEXEC | libc::O_NOFOLLOW | libc::O_NONBLOCK\n        } else {\n            libc::O_RDONLY | libc::O_CLOEXEC | libc::O_NOFOLLOW | libc::O_DIRECTORY\n        };\n        use std::os::fd::AsRawFd;\n        // SAFETY: the directory fd and component C string are valid for this\n        // call and flags require no variadic mode.\n        let fd = unsafe { libc::openat(current.as_raw_fd(), component.as_ptr(), flags) };\n        if fd < 0 {\n            return Err(io::Error::last_os_error());\n        }\n        // SAFETY: `fd` is newly owned after the successful `openat`.\n        current = unsafe { File::from_raw_fd(fd) };\n        opened_leaf = leaf;\n    }\n\n    if !opened_leaf {\n        return Err(io::Error::new(\n            io::ErrorKind::InvalidInput,\n            \"external credential path must name a file\",\n        ));\n    }\n    let metadata = current.metadata()?;\n    if !metadata.file_type().is_file() {\n        return Err(io::Error::new(\n            io::ErrorKind::InvalidInput,\n            \"external credential path must name a regular file\",\n        ));\n    }\n    if require_owner_only {\n        use std::os::unix::fs::MetadataExt as _;\n        // SAFETY: geteuid(2) dereferences no pointers.\n        if metadata.uid() != unsafe { libc::geteuid() }\n            || metadata.mode() & 0o077 != 0\n            || metadata.nlink() != 1\n        {","sourceCodeStart":199,"sourceCodeEnd":235,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/external_credentials.rs#L199-L235","documentation":"After walking every component with `openat`, `open_secure_regular_file` checks that the final component actually opened a leaf file (and subsequently that the metadata is a regular file). A path that ends in a directory or vanishes into a non-leaf (e.g. trailing slash semantics) is rejected with this `InvalidInput` error — the credential API only reads files, not directories.","triggerScenarios":"`read_to_string`/`read_codewhale_owned_to_string` called with a path that names a directory (e.g. `/home/me/.config/codewhale/` or a credentials directory rather than a file).","commonSituations":"Config value points at a directory (`~/.ssh`, `~/.config/codewhale/credentials/`) instead of the file inside it; trailing slash in a copied path; the expected credential file was never created so only the parent directory exists.","solutions":["Point the path at the credential file itself, not its directory: e.g. `/home/me/.config/codewhale/credentials.json`.","Remove any trailing `/` from the configured path.","Verify the credential file exists: `ls -la <path>` — if only the directory is present, complete the credential setup/login step that creates the file.","Check whether the tool that should have written the credential file ran and had permission to create it."],"exampleFix":"// before\nlet creds = read_to_string(\"/home/me/.config/codewhale/credentials/\")?; // directory\n// after\nlet creds = read_to_string(\"/home/me/.config/codewhale/credentials.json\")?;","handlingStrategy":"validation","validationCode":"let md = std::fs::metadata(&path)?; // pre-check\nif !md.is_file() {\n    return Err(anyhow::anyhow!(\"credential path must be a file, not a directory: {path:?}\"));\n}","typeGuard":"fn names_regular_file(p: &Path) -> bool {\n    std::fs::metadata(p).map(|m| m.is_file()).unwrap_or(false)\n}","tryCatchPattern":"match read_to_string(&cred_path) {\n    Err(e) if e.to_string().contains(\"must name a file\") => {\n        eprintln!(\"{cred_path:?} points at a directory; set the full path to the credential file.\");\n    }\n    other => other?,\n}","preventionTips":["Configure the full file path of credentials, never the containing directory.","Strip trailing slashes from copied paths before storing them.","Verify the credential file exists after login/setup flows complete.","Check file-vs-directory in your config loader and fail with a precise message."],"tags":["filesystem","path-validation","credentials","is-a-directory"],"backgroundTag":"path-is-not-a-directory","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}