{"record":{"id":"380bc03e7241bf00","repo":"Hmbown/CodeWhale","slug":"codewhale-credentials-path-must-be-a-directory","errorCode":null,"errorMessage":"Codewhale credentials path must be a directory","messagePattern":"Codewhale credentials path must be a directory","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/config/src/xai_credentials.rs","lineNumber":667,"sourceCode":"        }\n        if fd < 0 {\n            return Err(std::io::Error::last_os_error()).with_context(|| {\n                format!(\n                    \"opening Codewhale credentials directory without following links: {}\",\n                    crate::quote_os_path(directory)\n                )\n            });\n        }\n        // SAFETY: `fd` is a newly owned descriptor on the success path above.\n        current = unsafe { File::from_raw_fd(fd) };\n    }\n    let metadata = current.metadata().with_context(|| {\n        format!(\n            \"inspecting Codewhale credentials directory {}\",\n            crate::quote_os_path(directory)\n        )\n    })?;\n    anyhow::ensure!(\n        metadata.is_dir(),\n        \"Codewhale credentials path must be a directory\"\n    );\n    // SAFETY: geteuid(2) dereferences no pointers.\n    anyhow::ensure!(\n        metadata.uid() == unsafe { libc::geteuid() },\n        \"Codewhale credentials directory must be owned by the current user\"\n    );\n    current\n        .set_permissions(fs::Permissions::from_mode(0o700))\n        .with_context(|| {\n            format!(\n                \"securing Codewhale credentials directory {}\",\n                crate::quote_os_path(directory)\n            )\n        })?;\n    Ok(XaiOAuthCredentialStore {\n        directory: directory.to_path_buf(),","sourceCodeStart":649,"sourceCodeEnd":685,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/config/src/xai_credentials.rs#L649-L685","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Point the configuration at the parent directory, not the token file.","Recreate the directory (mkdir -p) and restore or re-authenticate credentials.","Remove a stale symlink or file occupying the path and recreate it as a directory.","Canonicalize and stat the path in your own setup code before calling open."],"exampleFix":"// before\nlet dir = Path::new(\"~/.codewhale/xai.oauth.v1.json\"); // a file\nlet store = XaiOAuthCredentialStore::open(dir)?;\n// after\nlet dir = Path::new(\"/home/me/.codewhale\");\nanyhow::ensure!(dir.metadata()?.is_dir(), \"expected directory\");\nlet store = XaiOAuthCredentialStore::open(dir)?;","handlingStrategy":"validation","validationCode":"anyhow::ensure!(std::fs::metadata(dir).map(|m| m.is_dir()).unwrap_or(false), \"credentials path must be a directory\");","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Configure the parent directory, never the token file path","mkdir the directory as the runtime user before first run","Recheck the path after restore/backup operations"],"tags":["filesystem","configuration","validation"],"backgroundTag":"path-is-not-a-directory","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"}