{"record":{"id":"38844e5136356d7e","repo":"Hmbown/CodeWhale","slug":"codewhale-credentials-directory-must-be-lexically","errorCode":null,"errorMessage":"Codewhale credentials directory must be lexically normalized: {}","messagePattern":"Codewhale credentials directory must be lexically normalized: (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/config/src/xai_credentials.rs","lineNumber":102,"sourceCode":"}\n\n/// Make an owned path absolute without resolving any filesystem component.\n/// Canonicalization is deliberately forbidden here: following an existing\n/// `credentials` symlink would erase the lexical Codewhale-owned boundary and\n/// turn an external directory into an apparently valid destination.\nfn lexical_absolute_path(path: &Path) -> Result<PathBuf> {\n    let absolute = if path.is_absolute() {\n        path.to_path_buf()\n    } else {\n        std::env::current_dir()\n            .context(\"resolving the Codewhale credentials directory\")?\n            .join(path)\n    };\n    if absolute\n        .components()\n        .any(|component| matches!(component, Component::CurDir | Component::ParentDir))\n    {\n        bail!(\n            \"Codewhale credentials directory must be lexically normalized: {}\",\n            crate::quote_os_path(&absolute)\n        );\n    }\n    Ok(absolute)\n}\n\npub fn xai_oauth_generation_path(generation: &str) -> Result<PathBuf> {\n    Ok(xai_oauth_credentials_dir()?.join(validate_xai_oauth_generation(generation)?))\n}\n\npub fn legacy_xai_oauth_path() -> Result<PathBuf> {\n    Ok(xai_oauth_credentials_dir()?.join(LEGACY_XAI_OAUTH_FILE_NAME))\n}\n\n/// Serialize every Codewhale-owned xAI OAuth lifecycle mutation across threads\n/// and processes while pinning the lexical credentials directory.\n///","sourceCodeStart":84,"sourceCodeEnd":120,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/config/src/xai_credentials.rs#L84-L120","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before\nexport CODEWHALE_HOME=\"$BASE/../state\"\n\n# after\nexport CODEWHALE_HOME=\"$(realpath \"$BASE/../state\")\"","handlingStrategy":"validation","validationCode":"use std::path::{Component, PathBuf};\n\nfn lexically_normal(p: &PathBuf) -> bool {\n    !p.components().any(|c| matches!(c, Component::CurDir | Component::ParentDir))\n}\n\nif let Ok(home) = std::env::var(\"CODEWHALE_HOME\") {\n    anyhow::ensure!(lexically_normal(&PathBuf::from(&home)), \"CODEWHALE_HOME must be absolute and normalized\");\n}","typeGuard":"fn is_lexically_normal(p: &std::path::Path) -> bool {\n    !p.components().any(|c| matches!(c, std::path::Component::CurDir | std::path::Component::ParentDir))\n}","tryCatchPattern":null,"preventionTips":["Export CODEWHALE_HOME only as a fully resolved absolute path (wrap in realpath)","Avoid building home paths by string concatenation of fragments"],"tags":["rust","credentials","path","environment-variable","validation"],"backgroundTag":"non-normalized-path","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}