Hmbown/CodeWhale · error · anyhow::Error
Codewhale credentials path must be a directory
Error message
Codewhale credentials path must be a directory
What it means
After pinning the final descriptor (every component opened with O_DIRECTORY | O_NOFOLLOW), the unix opener verifies the descriptor really is a directory. A non-directory normally fails earlier with an ENOTDIR io error at openat time, so this ensure is the fail-closed belt for exotic filesystems or a race that swaps a component between open and fstat.
Source
Thrown at crates/config/src/xai_credentials.rs:536
}
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"
);
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(),
directory_handle: current,View on GitHub (pinned to 8880682c63)
Solutions
- Replace the offending file with a real directory: remove it, then let `codewhale auth xai-device` recreate $CODEWHALE_HOME/credentials
- Verify $CODEWHALE_HOME sits on a normal local filesystem
- Re-run once if another process was moving home components concurrently
Example fix
# before: $CODEWHALE_HOME/credentials is a plain file file "$CODEWHALE_HOME/credentials" # ASCII text # after rm "$CODEWHALE_HOME/credentials" codewhale auth xai-device # store recreates it as a directory
Defensive patterns
Strategy: validation
Validate before calling
match std::fs::metadata(&dir) {
Ok(m) if m.is_dir() => Ok(()),
Ok(_) => anyhow::bail!("credentials path exists but is not a directory"),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(()), // store will create it
Err(e) => Err(e.into()),
} Try / catch
match codewhale_config::with_xai_oauth_lifecycle_lock(op) {
Ok(v) => v,
Err(e) if e.to_string().contains("must be a directory") => {
// remove the wrong-typed path and let the store recreate it
let _ = std::fs::remove_file(&dir);
codewhale_config::with_xai_oauth_lifecycle_lock(op)?
}
Err(e) => return Err(e),
} Prevention
- Do not pre-create $CODEWHALE_HOME/credentials as a file
- Let the store create the directory during the first login
- Check the path shape after restoring home directories from backups
When it happens
Trigger: $CODEWHALE_HOME/credentials exists as a regular file or FIFO (usually surfacing earlier as the ENOTDR open error instead); a FUSE/odd filesystem that permits O_DIRECTORY opens on non-directories; a concurrent process replacing a home-directory component mid-open.
Common situations: A leftover file named `credentials` in the home directory; home directories on unusual network or FUSE mounts; interrupted home-directory migrations.
Related errors
- Codewhale credentials directory must be owned by the current
- xAI OAuth path must be a regular file
- xAI OAuth file must not have multiple filesystem links
- invalid Codewhale-owned xAI OAuth basename
- xAI OAuth private basename must be one UTF-8 path component
AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16).
Data as JSON: /api/errors/380bc03e7241bf00.
Report an issue: GitHub.