{"record":{"id":"c5b8ff6049ef40bf","repo":"Hmbown/CodeWhale","slug":"external-credential-path-must-resolve-to-an-absolu","errorCode":null,"errorMessage":"external credential path must resolve to an absolute path: {}","messagePattern":"external credential path must resolve to an absolute path: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/config/src/external_credentials.rs","lineNumber":139,"sourceCode":"    let mut normalized = PathBuf::new();\n    for component in absolute.components() {\n        match component {\n            Component::Prefix(prefix) => normalized.push(prefix.as_os_str()),\n            Component::RootDir => normalized.push(component.as_os_str()),\n            Component::CurDir => {}\n            Component::ParentDir => {\n                if !normalized.pop() {\n                    bail!(\n                        \"external credential path escapes its absolute root: {}\",\n                        quote_os_path(&absolute)\n                    );\n                }\n            }\n            Component::Normal(part) => normalized.push(part),\n        }\n    }\n    if !normalized.is_absolute() {\n        bail!(\n            \"external credential path must resolve to an absolute path: {}\",\n            quote_os_path(&normalized)\n        );\n    }\n    Ok(normalized)\n}\n\n/// The side-effect envelope Codewhale may use for an external credential.\n#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]\n#[serde(rename_all = \"snake_case\")]\npub enum ExternalCredentialAccess {\n    /// Do not inspect or access the external credential store.\n    #[default]\n    Disabled,\n    /// Read the exact selected file without refreshing or rewriting it.\n    ReadOnly,\n    /// Permit a documented preservation adapter to refresh and rewrite it.\n    Managed,","sourceCodeStart":121,"sourceCodeEnd":157,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/config/src/external_credentials.rs#L121-L157","documentation":"Thrown by resolve_external_credential_path when, after lexically normalizing components, the resulting path is not absolute. The contract is that external credential sources must be absolute paths; relative paths are ambiguous because they would resolve against an unpredictable working directory, so the resolver rejects them outright.","triggerScenarios":"Passing a relative path such as \"creds/token.txt\" or \"~/token\" (tilde is not expanded by this function) to resolve_external_credential_path; on Windows, a path without a drive/prefix after normalization.","commonSituations":"Users typing relative paths into config for external credential sources; shell-style `~` paths pasted verbatim into configuration; scripts built from CWD-relative fragments.","solutions":["Expand `~` and relative segments to a full absolute path before calling (e.g. use dirs::home_dir() or std::env::current_dir().join())","Store absolute paths in the external credential source configuration","On Windows include the drive letter (C:\\...) in the configured path","Use the canonicalized path from std::fs::canonicalize before handing it to the resolver"],"exampleFix":"// before\nresolve_external_credential_path(\"~/secrets/token.txt\")?;\n// after\nlet home = std::env::var(\"HOME\").unwrap();\nresolve_external_credential_path(format!(\"{home}/secrets/token.txt\"))?;","handlingStrategy":"validation","validationCode":"fn ensure_absolute(p: &std::path::Path) -> Option<std::path::PathBuf> {\n    if p.is_absolute() { Some(p.to_path_buf()) }\n    else { std::env::current_dir().ok().map(|d| d.join(p)) }\n}\n// expand '~' via home_dir() before resolving","typeGuard":"fn is_absolute_path(p: &str) -> bool { std::path::Path::new(p).is_absolute() }","tryCatchPattern":null,"preventionTips":["Store absolute paths (with drive letter on Windows) in configuration","Expand `~` at config-load time, not at read time","Prefer fs::canonicalize when recording paths"],"tags":["config","filesystem","path"],"backgroundTag":"invalid-argument-value","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}