{"record":{"id":"bea83247d8394810","repo":"Hmbown/CodeWhale","slug":"refusing-to-replace-an-existing-xai-oauth-retireme","errorCode":null,"errorMessage":"refusing to replace an existing xAI OAuth retirement path","messagePattern":"refusing to replace an existing xAI OAuth retirement path","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/config/src/xai_credentials.rs","lineNumber":735,"sourceCode":"        if unsafe { libc::unlinkat(self.directory_handle.as_raw_fd(), name.as_ptr(), 0) } != 0 {\n            let error = std::io::Error::last_os_error();\n            if error.kind() == std::io::ErrorKind::NotFound {\n                return Ok(false);\n            }\n            return Err(error).context(\"removing Codewhale-owned xAI OAuth file\");\n        }\n        Ok(true)\n    }\n\n    fn rename_raw(&self, from: &str, to: &str) -> Result<()> {\n        use std::os::fd::AsRawFd as _;\n        validate_private_basename(from)?;\n        validate_private_basename(to)?;\n        let source = self\n            .open_owned_file_for_read(from)?\n            .context(\"xAI OAuth source disappeared before retirement\")?;\n        validate_owned_file_handle(&source, &self.directory.join(from))?;\n        anyhow::ensure!(\n            self.open_owned_file_for_read(to)?.is_none(),\n            \"refusing to replace an existing xAI OAuth retirement path\"\n        );\n        drop(source);\n        let from = CString::new(from).context(\"xAI OAuth basename contains an interior NUL\")?;\n        let to = CString::new(to).context(\"xAI OAuth basename contains an interior NUL\")?;\n        // SAFETY: both names are one component relative to the same pinned\n        // directory descriptor.\n        if unsafe {\n            libc::renameat(\n                self.directory_handle.as_raw_fd(),\n                from.as_ptr(),\n                self.directory_handle.as_raw_fd(),\n                to.as_ptr(),\n            )\n        } != 0\n        {\n            return Err(std::io::Error::last_os_error()).context(\"retiring xAI OAuth file\");","sourceCodeStart":717,"sourceCodeEnd":753,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/crates/config/src/xai_credentials.rs#L717-L753","documentation":"During a revocation on unix, each active credential is renamed to a tombstone `.xai-oauth-retired-<pid>-<nanos>-<counter>.tmp`; rename_raw fail-closed refuses to overwrite an existing destination. A collision requires the same pid, an identical nanosecond timestamp, and the same counter, so in practice this only happens after clock skew (VM snapshot restore, NTP step) combined with pid reuse.","triggerScenarios":"with_xai_oauth_revocation_transaction / stage_revocation where a stale tombstone from an earlier run received an identical generated name because SystemTime::now went backwards (VM restore, clock step) and the pid was reused.","commonSituations":"Laptops or VMs restored from snapshots with rewound clocks; CI runners with unstable clocks; otherwise effectively unreachable.","solutions":["Retry the operation: the fresh timestamp/counter nonce makes a second attempt collide with near-zero probability","When no codewhale process is running, delete stale .xai-oauth-retired-*.tmp files in $CODEWHALE_HOME/credentials","If the clock was rewound (VM restore), let NTP stabilize time before retrying"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"let mut attempt = 0;\nloop {\n    match codewhale_config::with_xai_oauth_revocation_transaction(&op) {\n        Ok(v) => break Ok(v),\n        Err(e) if e.to_string().contains(\"refusing to replace an existing xAI OAuth retirement path\")\n            && attempt < 2 =>\n        {\n            attempt += 1; // nonce collision: a retry gets a fresh timestamp\n        }\n        Err(e) => break Err(e),\n    }\n}","preventionTips":["Keep system clocks stable (NTP) around revocation transactions, especially after VM restores","Clean stale .xai-oauth-retired-*.tmp tombstones when no codewhale process runs","Treat this error as transient in orchestrators; do not escalate on first occurrence"],"tags":["unix","filesystem","race-condition","transient","xai-oauth"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}