{"record":{"id":"d7f0e8afa104811c","repo":"Hmbown/CodeWhale","slug":"task-store-is-busy-state-is-unavailable","errorCode":null,"errorMessage":"Task store is busy; state is unavailable","messagePattern":"Task store is busy; state is unavailable","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/task_manager.rs","lineNumber":2967,"sourceCode":"\n    /// Acquire the cross-process task-store lock.\n    ///\n    /// Polls with exponential backoff (5ms → 50ms) instead of a flat 5ms\n    /// interval: under contention the old shape woke ~200×/s for up to its\n    /// whole five-second deadline (#6211 R7c). The deadline and the busy\n    /// error are unchanged. What this does not do: it does not add the\n    /// in-process mutex the issue also suggested — in-process contenders\n    /// just back off against the same file lock.\n    async fn lock_store(&self) -> Result<RuntimeProcessOwnerLock> {\n        let path = self.cfg.data_dir.join(\"task-store.lock\");\n        let deadline = Instant::now() + Duration::from_secs(5);\n        let mut wait = Duration::from_millis(5);\n        loop {\n            if let Some(owner) = RuntimeProcessOwnerLock::try_acquire_file(&path, true)? {\n                return Ok(owner);\n            }\n            if Instant::now() >= deadline {\n                bail!(\"Task store is busy; state is unavailable\");\n            }\n            sleep(wait).await;\n            wait = (wait * 2).min(Duration::from_millis(50));\n        }\n    }\n\n    fn refresh_locked(&self, state: &mut ManagerState) -> Result<()> {\n        let loaded = load_state(&self.tasks_dir, &self.queue_path)?;\n        state.tasks = loaded.tasks;\n        state.queue = loaded.queue;\n        for (id, events) in &state.pending_events {\n            let task = state\n                .tasks\n                .get_mut(id)\n                .context(\"Pending task disappeared\")?;\n            self.require_execution_owner(task)?;\n            for event in events {\n                self.apply_event_to_task(task, event.clone())?;","sourceCodeStart":2949,"sourceCodeEnd":2985,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/task_manager.rs#L2949-L2985","documentation":"Opening the task store requires acquiring an exclusive cross-process file lock (RuntimeProcessOwnerLock). The manager retries with exponential backoff (5ms doubling to 50ms) until a deadline; if the lock is still held at the deadline, it gives up and reports the store as busy/unavailable.","triggerScenarios":"Calling any state-loading operation on the task store while another process holds RuntimeProcessOwnerLock for the whole backoff window (deadline exceeded).","commonSituations":"Two TUI/CLI instances running against the same task store; a crashed process leaving a stale lock file; very long-running foreground operation in a peer process exceeding the (short) wait deadline.","solutions":["Close the other process using the same task store, or wait and retry the operation","Remove the stale lock file only after confirming no live owner process exists","Point this instance at a different task-store directory to avoid contention"],"exampleFix":"// before: parallel instances contending on the same store\ncodewhale --task-store ~/.codewhale/tasks &\ncodewhale --task-store ~/.codewhale/tasks &  // busy\n// after: isolate per session\ncodewhale --task-store /tmp/cw-tasks-$SESSION_ID","handlingStrategy":"retry","validationCode":"if store_lock_is_held(&path)? { eprintln!(\"store busy; retry later\"); return Ok(()); }","typeGuard":"fn store_available(path: &Path) -> bool {\n    RuntimeProcessOwnerLock::try_acquire_file(path, true).map(|o| o.is_some()).unwrap_or(false)\n}","tryCatchPattern":"let mut attempts = 0;\nloop {\n    match manager.load_state().await {\n        Err(e) if e.to_string().contains(\"Task store is busy\") && attempts < 5 => {\n            attempts += 1;\n            tokio::time::sleep(Duration::from_millis(100 * attempts)).await;\n        }\n        other => break other?,\n    }\n}","preventionTips":["Run a single instance per task-store directory","After a crash, verify and clear stale lock files before restarting","Keep critical sections short so locks are released quickly"],"tags":["locking","concurrency","timeout","task-store"],"backgroundTag":"request-timeout","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-22T06:17:15.046Z"}