{"record":{"id":"489b2b21db7b5145","repo":"Hmbown/CodeWhale","slug":"refusing-to-replace-xai-oauth-generation","errorCode":null,"errorMessage":"refusing to replace xAI OAuth generation","messagePattern":"refusing to replace xAI OAuth generation","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/config/src/xai_credentials.rs","lineNumber":1660,"sourceCode":"        match File::open(self.directory.join(name)) {\n            Ok(file) => Ok(Some(file)),\n            Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(None),\n            Err(error) => Err(error.into()),\n        }\n    }\n\n    fn open_internal_file(&self, name: &str) -> Result<File> {\n        validate_private_basename(name)?;\n        Ok(fs::OpenOptions::new()\n            .read(true)\n            .write(true)\n            .create(true)\n            .open(self.directory.join(name))?)\n    }\n\n    fn write_owned_file(&self, name: &str, bytes: &[u8], allow_replace: bool) -> Result<()> {\n        let path = self.directory.join(name);\n        anyhow::ensure!(\n            allow_replace || !path.exists(),\n            \"refusing to replace xAI OAuth generation\"\n        );\n        crate::persistence::atomic_write(&path, bytes)\n    }\n\n    fn remove_raw(&self, name: &str) -> Result<bool> {\n        validate_private_basename(name)?;\n        match fs::remove_file(self.directory.join(name)) {\n            Ok(()) => Ok(true),\n            Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(false),\n            Err(error) => Err(error.into()),\n        }\n    }\n\n    fn rename_raw(&self, from: &str, to: &str) -> Result<()> {\n        validate_private_basename(from)?;\n        validate_private_basename(to)?;","sourceCodeStart":1642,"sourceCodeEnd":1678,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/config/src/xai_credentials.rs#L1642-L1678","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Close other running instances of the app that share the credentials store, then retry the operation.","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.","Log out (which runs cleanup removing only Codewhale-owned xAI files) and log back in to start a clean generation sequence.","If writing via the library, pass allow_replace=true only on the explicit pointer-replacement path; never to work around the guard."],"exampleFix":"// before\nstore.write_owned_file(\"token-gen-1.json\", &bytes, false)?; // exists -> error\n// after: write the new generation under a fresh name, then switch the pointer\nstore.write_owned_file(\"token-gen-2.json\", &bytes, false)?;\nstore.switch_pointer(\"token-gen-2.json\")?;","handlingStrategy":"try-catch","validationCode":"// Rust, before persisting a new generation\nlet target = store.directory().join(&generation_name);\nif target.exists() {\n    // pick a fresh generation name or run logout cleanup first\n}","typeGuard":null,"tryCatchPattern":"// match on the store error and recover by cleaning up + re-auth\nmatch store.save_generation(&bytes) {\n    Err(e) if e.to_string().contains(\"refusing to replace\") => {\n        store.logout_cleanup()?;\n        store.save_generation(&bytes)?;\n    }\n    r => r?,\n}","preventionTips":["Run only one app instance that shares the xAI credentials store.","After a failed persist, clean up stale generations (logout) before retrying.","Never pass allow_replace=true outside the explicit pointer-switch code path."],"tags":["filesystem","oauth","concurrency","credentials"],"backgroundTag":"file-already-exists","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"}