{"record":{"id":"15bb23ab0a182333","repo":"Hmbown/CodeWhale","slug":"xai-oauth-file-must-be-owned-by-the-current-user","errorCode":null,"errorMessage":"xAI OAuth file must be owned by the current user","messagePattern":"xAI OAuth file must be owned by the current user","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/config/src/xai_credentials.rs","lineNumber":769,"sourceCode":"        } != 0\n        {\n            return Err(std::io::Error::last_os_error()).context(\"retiring xAI OAuth file\");\n        }\n        Ok(())\n    }\n}\n\n#[cfg(unix)]\nfn validate_owned_file_handle(file: &File, path: &Path) -> Result<fs::Metadata> {\n    use std::os::unix::fs::MetadataExt as _;\n    let metadata = file.metadata().with_context(|| {\n        format!(\n            \"inspecting Codewhale-owned xAI OAuth file {}\",\n            crate::quote_os_path(path)\n        )\n    })?;\n    anyhow::ensure!(metadata.is_file(), \"xAI OAuth path must be a regular file\");\n    anyhow::ensure!(\n        metadata.uid() == unsafe { libc::geteuid() },\n        \"xAI OAuth file must be owned by the current user\"\n    );\n    anyhow::ensure!(\n        metadata.nlink() == 1,\n        \"xAI OAuth file must not have multiple filesystem links\"\n    );\n    Ok(metadata)\n}\n\n#[cfg(windows)]\nfn open_owned_credentials_directory(directory: &Path) -> Result<XaiOAuthCredentialStore> {\n    use std::os::windows::fs::OpenOptionsExt as _;\n    use windows_sys::Win32::Storage::FileSystem::{\n        FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, FILE_GENERIC_READ,\n        FILE_SHARE_READ, FILE_SHARE_WRITE, WRITE_DAC, WRITE_OWNER,\n    };\n","sourceCodeStart":751,"sourceCodeEnd":787,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/config/src/xai_credentials.rs#L751-L787","documentation":"On Unix, validate_owned_file_handle requires the credential file's uid to equal the effective uid (xai_credentials.rs:769). A credential file owned by another user could have been planted or modified outside the store's control, so reads, writes, and retirement refuse it.","triggerScenarios":"Any read/rename/write validation where the credential file (xai-auth.json or a generation file) is owned by a different uid — created under sudo, restored from a backup with foreign uid, or written by another account on a shared machine.","commonSituations":"Logging in once with sudo so root owns the token file; home restored from backup under a different numeric uid; multi-user hosts sharing a home over NFS with mismatched ids.","solutions":["Check with stat -c '%u' <file> and chown the file back: sudo chown \"$(id -u)\" <file>","Or delete the foreign-owned credential file and log in again as the correct user","Never run the OAuth login flow under sudo or as another account"],"exampleFix":"# before\n$ stat -c '%u' ~/.codewhale/xai/xai-auth-*.json\n0\n\n# after\n$ sudo chown \"$(id -u)\" ~/.codewhale/xai/xai-auth-*.json\n$ codewhale login --provider xai","handlingStrategy":"validation","validationCode":"#[cfg(unix)]\nfn credential_files_owned_by_current_user(store_dir: &Path) -> bool {\n    use std::os::unix::fs::MetadataExt;\n    let euid = unsafe { libc::geteuid() };\n    std::fs::read_dir(store_dir)\n        .map(|entries| {\n            entries.filter_map(|e| e.ok()).all(|e| {\n                e.metadata().map(|m| m.uid() == euid).unwrap_or(false)\n            })\n        })\n        .unwrap_or(false)\n}","typeGuard":null,"tryCatchPattern":"match store.read(&name) {\n    Err(e) if e.to_string().contains(\"file must be owned by the current user\") => {\n        eprintln!(\"fix ownership first: sudo chown \\\"$(id -u)\\\" {}\", store.directory().join(&name).display());\n        Err(e)\n    }\n    other => other,\n}","preventionTips":["Run the OAuth login flow only as the user who owns the credentials directory","After restores, chown credential files to the current uid","On shared hosts, keep per-user credentials directories"],"tags":["ownership","unix","credentials","permissions","oauth","codewhale"],"backgroundTag":"ownership-mismatch","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}