Hmbown/CodeWhale · error

refusing to replace xAI OAuth generation

Error message

refusing to replace xAI OAuth generation

What it means

write_owned_file in XaiOAuthCredentialStore refuses to overwrite an existing generation file unless allow_replace is true. xAI OAuth tokens are stored as immutable generations with a pointer naming the live one; silently overwriting a generation would corrupt history and race with concurrent writers, so the store requires explicit intent (the pointer-switch path) before replacement.

Solutions

  1. Close other running instances of the app that share the credentials store, then retry the operation.
  2. Inspect the store directory for stale generation files from a failed persist; if the pointer names another generation, the stale file can be removed via logout/cleanup before re-authenticating.
  3. Log out (which runs cleanup removing only Codewhale-owned xAI files) and log back in to start a clean generation sequence.
  4. If writing via the library, pass allow_replace=true only on the explicit pointer-replacement path; never to work around the guard.

Example fix

// before
store.write_owned_file("token-gen-1.json", &bytes, false)?; // exists -> error
// after: write the new generation under a fresh name, then switch the pointer
store.write_owned_file("token-gen-2.json", &bytes, false)?;
store.switch_pointer("token-gen-2.json")?;
Defensive patterns

Strategy: try-catch

Validate before calling

// Rust, before persisting a new generation
let target = store.directory().join(&generation_name);
if target.exists() {
    // pick a fresh generation name or run logout cleanup first
}

Try / catch

// match on the store error and recover by cleaning up + re-auth
match store.save_generation(&bytes) {
    Err(e) if e.to_string().contains("refusing to replace") => {
        store.logout_cleanup()?;
        store.save_generation(&bytes)?;
    }
    r => r?,
}

Prevention

When it happens

Trigger: Calling an internal store write for a generation basename that already exists on disk with allow_replace=false — e.g. saving a new token generation whose filename collides with an existing one (re-login producing the same generation name), or a stale pointer/retry re-attempting a completed write.

Common situations: Clock or counter reuse produced the same generation filename twice; a partially failed previous persist left the generation file in place and a retry hits the guard; running two app instances concurrently writing the same store.

Understand the failure class

Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/489b2b21db7b5145. Report an issue: GitHub.

Appendix: source

Thrown at crates/config/src/xai_credentials.rs:1660

        match File::open(self.directory.join(name)) {
            Ok(file) => Ok(Some(file)),
            Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(None),
            Err(error) => Err(error.into()),
        }
    }

    fn open_internal_file(&self, name: &str) -> Result<File> {
        validate_private_basename(name)?;
        Ok(fs::OpenOptions::new()
            .read(true)
            .write(true)
            .create(true)
            .open(self.directory.join(name))?)
    }

    fn write_owned_file(&self, name: &str, bytes: &[u8], allow_replace: bool) -> Result<()> {
        let path = self.directory.join(name);
        anyhow::ensure!(
            allow_replace || !path.exists(),
            "refusing to replace xAI OAuth generation"
        );
        crate::persistence::atomic_write(&path, bytes)
    }

    fn remove_raw(&self, name: &str) -> Result<bool> {
        validate_private_basename(name)?;
        match fs::remove_file(self.directory.join(name)) {
            Ok(()) => Ok(true),
            Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(false),
            Err(error) => Err(error.into()),
        }
    }

    fn rename_raw(&self, from: &str, to: &str) -> Result<()> {
        validate_private_basename(from)?;
        validate_private_basename(to)?;

View on GitHub (pinned to 73e0f67d83)