Hmbown/CodeWhale · error

Codewhale credentials path must be a directory

Error message

Codewhale credentials path must be a directory

What it means

After opening the directory, the store stats it and requires the resulting metadata to be a directory. If the path exists but is a regular file, symlink-to-file, or other non-directory, opening the credential store is refused because all operations rely on directory-relative file descriptors.

Solutions

  1. Point the configuration at the parent directory, not the token file.
  2. Recreate the directory (mkdir -p) and restore or re-authenticate credentials.
  3. Remove a stale symlink or file occupying the path and recreate it as a directory.
  4. Canonicalize and stat the path in your own setup code before calling open.

Example fix

// before
let dir = Path::new("~/.codewhale/xai.oauth.v1.json"); // a file
let store = XaiOAuthCredentialStore::open(dir)?;
// after
let dir = Path::new("/home/me/.codewhale");
anyhow::ensure!(dir.metadata()?.is_dir(), "expected directory");
let store = XaiOAuthCredentialStore::open(dir)?;
Defensive patterns

Strategy: validation

Validate before calling

anyhow::ensure!(std::fs::metadata(dir).map(|m| m.is_dir()).unwrap_or(false), "credentials path must be a directory");

Prevention

When it happens

Trigger: Calling open (or the cleanup/test entry points) with a directory path that actually points to a regular file — e.g. the config path points at a file that replaced a removed directory.

Common situations: A backup/restore replaced the directory with a tar file; the credentials path env var accidentally set to the token file itself rather than its parent directory; a stale symlink now resolving to a file.

Related errors


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

Appendix: source

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

        }
        if fd < 0 {
            return Err(std::io::Error::last_os_error()).with_context(|| {
                format!(
                    "opening Codewhale credentials directory without following links: {}",
                    crate::quote_os_path(directory)
                )
            });
        }
        // SAFETY: `fd` is a newly owned descriptor on the success path above.
        current = unsafe { File::from_raw_fd(fd) };
    }
    let metadata = current.metadata().with_context(|| {
        format!(
            "inspecting Codewhale credentials directory {}",
            crate::quote_os_path(directory)
        )
    })?;
    anyhow::ensure!(
        metadata.is_dir(),
        "Codewhale credentials path must be a directory"
    );
    // SAFETY: geteuid(2) dereferences no pointers.
    anyhow::ensure!(
        metadata.uid() == unsafe { libc::geteuid() },
        "Codewhale credentials directory must be owned by the current user"
    );
    current
        .set_permissions(fs::Permissions::from_mode(0o700))
        .with_context(|| {
            format!(
                "securing Codewhale credentials directory {}",
                crate::quote_os_path(directory)
            )
        })?;
    Ok(XaiOAuthCredentialStore {
        directory: directory.to_path_buf(),

View on GitHub (pinned to 73e0f67d83)