{"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":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/config/src/xai_credentials.rs","lineNumber":536,"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    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(),\n        directory_handle: current,","sourceCodeStart":518,"sourceCodeEnd":554,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/crates/config/src/xai_credentials.rs#L518-L554","documentation":"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.","triggerScenarios":"$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.","commonSituations":"A leftover file named `credentials` in the home directory; home directories on unusual network or FUSE mounts; interrupted home-directory migrations.","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"],"exampleFix":"# before: $CODEWHALE_HOME/credentials is a plain file\nfile \"$CODEWHALE_HOME/credentials\"   # ASCII text\n\n# after\nrm \"$CODEWHALE_HOME/credentials\"\ncodewhale auth xai-device   # store recreates it as a directory","handlingStrategy":"validation","validationCode":"match std::fs::metadata(&dir) {\n    Ok(m) if m.is_dir() => Ok(()),\n    Ok(_) => anyhow::bail!(\"credentials path exists but is not a directory\"),\n    Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(()), // store will create it\n    Err(e) => Err(e.into()),\n}","typeGuard":null,"tryCatchPattern":"match codewhale_config::with_xai_oauth_lifecycle_lock(op) {\n    Ok(v) => v,\n    Err(e) if e.to_string().contains(\"must be a directory\") => {\n        // remove the wrong-typed path and let the store recreate it\n        let _ = std::fs::remove_file(&dir);\n        codewhale_config::with_xai_oauth_lifecycle_lock(op)?\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["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"],"tags":["unix","filesystem","security","xai-oauth"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}