Hmbown/CodeWhale · error · std::io::Error
InvalidInput
InvalidInput
Error message
external credential path must be absolute
What it means
open_secure_regular_file (Unix) requires external credential paths to be absolute before it walks from / with openat, refusing anything relative as the first gate of the no-follow traversal that opens the exact granted file. Relative paths fail immediately with InvalidInput — no cwd-based resolution is attempted, which is deliberate: a consented credential path must be unambiguous.
Solutions
- Use a fully expanded absolute path: export PROVIDER_CREDENTIALS=/home/user/.config/provider/token.json
- Expand ~ at assignment time in the shell: export PROVIDER_CREDENTIALS="$HOME/.config/provider/token.json"
- In code, absolutize against a config root or canonicalize before passing the path into the credential reader
Example fix
# before export PROVIDER_CREDENTIALS="~/.config/provider/token.json" # stays literal, relative # after export PROVIDER_CREDENTIALS="$HOME/.config/provider/token.json" # expanded, absolute
Defensive patterns
Strategy: validation
Validate before calling
let path = Path::new(&value);
if !path.is_absolute() {
return Err(format!("credential path must be absolute, got: {value:?}"));
} Type guard
fn is_absolute_credential_path(p: &Path) -> bool {
p.is_absolute()
} Prevention
- Expand ~ at assignment time ($HOME/...) — env vars never expand it for you
- Validate env/config path values with is_absolute() at load time and name the variable in the error
- Prefer absolutizing against a known config root in code instead of trusting user input
When it happens
Trigger: An external-credential grant/config holding a relative value such as "./token" or "keys/api.json", or a tilde path like "~/.config/provider/token" — the OS does not expand ~ inside environment variables, so it stays relative and is rejected here.
Common situations: Env-based config written in .env files or scripts where the author assumed ~ expansion; running from a different working directory than when the relative path happened to work; values copy-pasted from docs showing shell-style paths.
Related errors
- external credential path must be absolute
- external credential path must be absolute and lexically…
- audited skill path does not match owned package
- bundle path escapes the config directory via a symlink…
- bundle path is absolute; only paths inside the config…
AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20).
Data as JSON: /api/errors/1e690c464e8304dc.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/external_credentials.rs:160
);
}
String::from_utf8(bytes).map(Some).with_context(|| {
format!(
"Codewhale-owned credential file {} is not valid UTF-8",
codewhale_config::quote_os_path(path)
)
})
}
#[cfg(unix)]
fn open_secure_regular_file(path: &Path, require_owner_only: bool) -> io::Result<File> {
use std::ffi::CString;
use std::os::fd::FromRawFd;
use std::os::unix::ffi::OsStrExt;
use std::path::Component;
if !path.is_absolute() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"external credential path must be absolute",
));
}
let root = CString::new("/").expect("static root contains no NUL");
// SAFETY: `root` is a valid C string and flags require no variadic mode.
let root_fd = unsafe {
libc::open(
root.as_ptr(),
libc::O_RDONLY | libc::O_DIRECTORY | libc::O_CLOEXEC,
)
};
if root_fd < 0 {
return Err(io::Error::last_os_error());
}
// SAFETY: `root_fd` is newly owned after the successful `open`.
let mut current = unsafe { File::from_raw_fd(root_fd) };View on GitHub (pinned to 0c42157ee5)