{"record":{"id":"d2e0fad1dc51f5e8","repo":"Hmbown/CodeWhale","slug":"xai-oauth-lifecycle-lock-was-poisoned","errorCode":null,"errorMessage":"xAI OAuth lifecycle lock was poisoned","messagePattern":"xAI OAuth lifecycle lock was poisoned","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/config/src/xai_credentials.rs","lineNumber":130,"sourceCode":"}\n\npub fn legacy_xai_oauth_path() -> Result<PathBuf> {\n    Ok(xai_oauth_credentials_dir()?.join(LEGACY_XAI_OAUTH_FILE_NAME))\n}\n\n/// Serialize every Codewhale-owned xAI OAuth lifecycle mutation across threads\n/// and processes while pinning the lexical credentials directory.\n///\n/// Lock order is always xAI lifecycle first, then config document. Callers must\n/// not invoke this function recursively.\npub fn with_xai_oauth_lifecycle_lock<T>(\n    operation: impl FnOnce(&XaiOAuthCredentialStore) -> Result<T>,\n) -> Result<T> {\n    static PROCESS_LOCK: OnceLock<Mutex<()>> = OnceLock::new();\n    let _process_guard = PROCESS_LOCK\n        .get_or_init(|| Mutex::new(()))\n        .lock()\n        .map_err(|_| anyhow::anyhow!(\"xAI OAuth lifecycle lock was poisoned\"))?;\n    let store = XaiOAuthCredentialStore::open()?;\n    let lock_file = store.open_lock_file()?;\n    let mut lock = fd_lock::RwLock::new(lock_file);\n    let _guard = lock.write().with_context(|| {\n        format!(\n            \"failed to acquire xAI OAuth lifecycle lock in {}\",\n            crate::quote_os_path(store.directory())\n        )\n    })?;\n    operation(&store)\n}\n\n/// Run an authority mode switch while the prior owned OAuth epoch is hidden\n/// from concurrent Codewhale readers. A failed authority mutation restores the\n/// old files; a successful mutation permanently removes them.\npub fn with_xai_oauth_revocation_transaction<T>(\n    operation: impl FnOnce() -> Result<T>,\n) -> Result<T> {","sourceCodeStart":112,"sourceCodeEnd":148,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/crates/config/src/xai_credentials.rs#L112-L148","documentation":"with_xai_oauth_lifecycle_lock first takes a process-wide std::sync::Mutex to serialize xAI OAuth mutations inside the process, then a cross-process fd-lock on the lock file. This error means the in-process Mutex was poisoned: a previous holder panicked while holding it, so lock() returned Err. The poison is per-process; a fresh process acquires cleanly, and on-disk state stays consistent because the file lock serializes across processes.","triggerScenarios":"Any panic inside the xAI OAuth lifecycle operation (or between acquisition and release) in this process; every subsequent with_xai_oauth_lifecycle_lock call in the same process then fails with this message.","commonSituations":"A long-lived process (daemon, test harness) where an earlier xAI login/logout/token-refresh panicked; later xAI operations all report the poisoned lock although the credential files on disk are fine.","solutions":["Restart the process; the poison clears on restart","Look earlier in the logs for the panic that poisoned the lock; that panic is the real bug, report it","Do not delete credential files to fix this; the fd-locked on-disk state was protected across the panic"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"Map this Err to a 'restart required' signal: the poison is process-local, so spawn a fresh process or disable xAI operations for this session instead of retrying in-process. Pair it with a log scan for the original panic.","preventionTips":["In long-lived hosts, run each xAI lifecycle operation in a short-lived subprocess so a panic cannot poison the host","Report the underlying panic found earlier in the logs; the poison is only the symptom","Never 'fix' it by deleting credential files: the fd-lock kept on-disk state consistent"],"tags":["concurrency","mutex","poison","xai","oauth"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}