Hmbown/CodeWhale · error

xAI OAuth credentials directory must be absolute

Error message

xAI OAuth credentials directory must be absolute

What it means

open_owned_credentials_directory (Unix) only accepts an absolute path for the credentials directory because it opens the directory with openat(2) and pins operations to the fd. Relative paths cannot be safely pinned, so the store refuses them up front.

Solutions

  1. Convert to absolute before calling: std::fs::canonicalize or join against a known root.
  2. Ensure the configured credentials directory in config/env is absolute.
  3. In tests, use temp_dir().join(...) which is already absolute.
  4. Fail loud at config load if the path is relative.

Example fix

// before
let store = XaiOAuthCredentialStore::open(Path::new("codewhale/creds"))?;
// after
let dir = std::fs::canonicalize("codewhale/creds")?;
let store = XaiOAuthCredentialStore::open(&dir)?;
Defensive patterns

Strategy: validation

Validate before calling

anyhow::ensure!(dir.is_absolute(), "credentials directory must be absolute");

Prevention

When it happens

Trigger: Calling XaiOAuthCredentialStore::open (or the logout-cleanup and test entry points) with a relative Path such as "codewhale/credentials" or "./creds".

Common situations: Deriving the directory from a relative config value or CWD-dependent string; tests constructing a TempDir path without canonicalization; env var pointing at a relative location.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


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

Appendix: source

Thrown at crates/config/src/xai_credentials.rs:584

}

fn validate_private_basename(name: &str) -> Result<()> {
    let path = Path::new(name);
    anyhow::ensure!(
        path.components().count() == 1
            && matches!(path.components().next(), Some(Component::Normal(_)))
            && path.file_name().and_then(|value| value.to_str()) == Some(name),
        "xAI OAuth private basename must be one UTF-8 path component"
    );
    Ok(())
}

#[cfg(unix)]
fn open_owned_credentials_directory(directory: &Path) -> Result<XaiOAuthCredentialStore> {
    use std::os::fd::FromRawFd as _;
    use std::os::unix::fs::{MetadataExt as _, PermissionsExt as _};

    anyhow::ensure!(
        directory.is_absolute(),
        "xAI OAuth credentials directory must be absolute"
    );
    // SAFETY: the literal root path contains no interior NUL and the returned
    // descriptor is immediately owned by `File`.
    let root_fd = unsafe {
        libc::open(
            c"/".as_ptr(),
            libc::O_RDONLY | libc::O_DIRECTORY | libc::O_CLOEXEC | libc::O_NOFOLLOW,
        )
    };
    if root_fd < 0 {
        return Err(std::io::Error::last_os_error()).context("opening filesystem root");
    }
    // SAFETY: `root_fd` is a newly owned descriptor on the success path above.
    let mut current = unsafe { File::from_raw_fd(root_fd) };
    for component in directory.components() {
        let Component::Normal(name) = component else {

View on GitHub (pinned to 73e0f67d83)