Hmbown/CodeWhale · error · anyhow::Error
Codewhale credentials directory must be lexically normalized
Error message
Codewhale credentials directory must be lexically normalized: {} What it means
Thrown by lexical_absolute_path when the Codewhale credentials directory (built from codewhale_home(), i.e. $CODEWHALE_HOME or the default ~/.codewhale, joined with 'credentials') contains a '.' or '..' component after being made absolute. Canonicalization is deliberately never used here (the comment forbids following the filesystem), so the path must already be lexically normalized.
Source
Thrown at crates/config/src/xai_credentials.rs:102
}
/// Make an owned path absolute without resolving any filesystem component.
/// Canonicalization is deliberately forbidden here: following an existing
/// `credentials` symlink would erase the lexical Codewhale-owned boundary and
/// turn an external directory into an apparently valid destination.
fn lexical_absolute_path(path: &Path) -> Result<PathBuf> {
let absolute = if path.is_absolute() {
path.to_path_buf()
} else {
std::env::current_dir()
.context("resolving the Codewhale credentials directory")?
.join(path)
};
if absolute
.components()
.any(|component| matches!(component, Component::CurDir | Component::ParentDir))
{
bail!(
"Codewhale credentials directory must be lexically normalized: {}",
crate::quote_os_path(&absolute)
);
}
Ok(absolute)
}
pub fn xai_oauth_generation_path(generation: &str) -> Result<PathBuf> {
Ok(xai_oauth_credentials_dir()?.join(validate_xai_oauth_generation(generation)?))
}
pub fn legacy_xai_oauth_path() -> Result<PathBuf> {
Ok(xai_oauth_credentials_dir()?.join(LEGACY_XAI_OAUTH_FILE_NAME))
}
/// Serialize every Codewhale-owned xAI OAuth lifecycle mutation across threads
/// and processes while pinning the lexical credentials directory.
///View on GitHub (pinned to 0c42157ee5)
Solutions
- Set CODEWHALE_HOME to an absolute, normalized path with no '.' or '..' segments
- Build it with realpath in shell: export CODEWHALE_HOME="$(realpath "$BASE/state")"
- Unset CODEWHALE_HOME to fall back to the default ~/.codewhale location
Example fix
# before export CODEWHALE_HOME="$BASE/../state" # after export CODEWHALE_HOME="$(realpath "$BASE/../state")"
Defensive patterns
Strategy: validation
Validate before calling
use std::path::{Component, PathBuf};
fn lexically_normal(p: &PathBuf) -> bool {
!p.components().any(|c| matches!(c, Component::CurDir | Component::ParentDir))
}
if let Ok(home) = std::env::var("CODEWHALE_HOME") {
anyhow::ensure!(lexically_normal(&PathBuf::from(&home)), "CODEWHALE_HOME must be absolute and normalized");
} Type guard
fn is_lexically_normal(p: &std::path::Path) -> bool {
!p.components().any(|c| matches!(c, std::path::Component::CurDir | std::path::Component::ParentDir))
} Prevention
- Export CODEWHALE_HOME only as a fully resolved absolute path (wrap in realpath)
- Avoid building home paths by string concatenation of fragments
When it happens
Trigger: CODEWHALE_HOME set to a path like /home/u/./state, /home/u/foo/../x, or a relative value such as ../state that gets joined onto the cwd; the components check then finds CurDir/ParentDir and bails.
Common situations: Isolating state per project by exporting CODEWHALE_HOME with a hand-built path; CI images reusing a generic HOME with trailing '/./'; scripts that concatenate path fragments without normalizing.
Related errors
- config path cannot be empty
- config path must include a file name
- project workspace path cannot be empty
- invalid Codewhale-owned xAI OAuth generation; expected xai-a
- Codewhale-owned xAI OAuth file {} exceeds the {} byte limit
AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20).
Data as JSON: /api/errors/38844e5136356d7e.
Report an issue: GitHub.