Hmbown/CodeWhale · critical
restoring retired xAI OAuth file
Error message
restoring retired xAI OAuth file {original} What it means
anyhow error raised by `XaiOAuthRevocation::rollback` (crates/config/src/xai_credentials.rs:522) when restoring a retired xAI OAuth file from its tombstone name back to the original name fails during `store.rename_raw(&tombstone, &original)`. Rollback is fail-closed: it never overwrites a file that unexpectedly appeared at the original name, and it keeps the first error while attempting every restoration. Called by `stage_revocation` and `mark_running_if_pending_with` when a config mutation fails and the old epoch must come back.
Solutions
- Inspect the credentials directory (`store.directory`) for stray files at both the original and tombstone names; resolve the collision manually before retrying (move the unexpected file aside).
- Close other Codewhale/xAI sessions and file-locking sync clients, then retry the operation.
- Fix directory permissions (ensure the store directory is writable by the current user) and free disk space if exhausted.
- After manual cleanup, re-run the credential revocation flow; rollback is idempotent per remaining tombstone and reports only the first failure.
Example fix
// before (failed) rollback(store)?; // error: restoring retired xAI OAuth file /home/me/.codewhale/xai.oauth // after: clear the collision first // mv /home/me/.codewhale/xai.oauth /home/me/.codewhale/xai.oauth.unexpected rollback(store)?;
Defensive patterns
Strategy: try-catch
Validate before calling
// before staging revocation
let dir = &store.directory;
for original in originals {
ensure!(!dir.join(original).exists(), "unexpected file at {} blocks rollback", dir.join(original).display());
} Try / catch
if let Err(e) = revocation.rollback(&store) {
log::error!("xAI OAuth rollback failed: {e:#}; manual cleanup of {} required", store.directory.display());
// surface to user; do not silently proceed with half-restored credentials
} Prevention
- Avoid concurrent Codewhale/xAI sessions sharing one credentials directory.
- Exclude the credentials directory from file-sync/AV scanning to avoid rename locks.
- Verify directory writability and free disk space before credential mutations.
- Clean stale tombstones from crashed runs before re-running revocation.
When it happens
Trigger: The tombstone file was deleted or locked between retirement and rollback; another process created a file at the original path so the rename is refused (fail-closed); the credentials directory lost write permission mid-operation; on Windows the rollback path is a no-op, so this only fires on non-Windows stores.
Common situations: Two Codewhale/xAI CLI sessions racing on the same credentials directory; AV or sync tools (Dropbox, OneDrive) holding locks on the OAuth files; disk-full or permission changes between revocation staging and failure-driven rollback; stale tombstones left by a crashed prior run.
Understand the failure class
Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.
Related errors
- restoring retired xAI OAuth file
- atomically replacing xAI OAuth credentials
- refusing to replace xAI OAuth generation
- agy OAuth token JSON carries no access token member
- agy OAuth token member
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/4abaf5c13d4fe98f.
Report an issue: GitHub.
Appendix: source
Thrown at crates/config/src/xai_credentials.rs:522
}
impl XaiOAuthRevocation {
/// Restore the old epoch after a config mutation fails. Restoration is
/// fail-closed: an unexpected replacement at an original name is never
/// overwritten.
pub fn rollback(self, store: &XaiOAuthCredentialStore) -> Result<()> {
#[cfg(windows)]
{
let _ = store;
Ok(())
}
#[cfg(not(windows))]
{
let mut first_error = None;
for (original, tombstone) in self.retired.into_iter().rev() {
let result = store.rename_raw(&tombstone, &original).with_context(|| {
format!(
"restoring retired xAI OAuth file {}",
crate::quote_os_path(&store.directory.join(original))
)
});
if result.is_err() && first_error.is_none() {
first_error = result.err();
}
}
if let Some(error) = first_error {
return Err(error);
}
Ok(())
}
}
/// Permanently remove retired bytes after the replacement config commits.
pub fn commit(self, store: &XaiOAuthCredentialStore) -> Result<usize> {
let mut removed = 0;
for (_original, _tombstone) in self.retired {View on GitHub (pinned to 73e0f67d83)