Hmbown/CodeWhale · error

Task store is busy; state is unavailable

Error message

Task store is busy; state is unavailable

What it means

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.

Solutions

  1. Close the other process using the same task store, or wait and retry the operation
  2. Remove the stale lock file only after confirming no live owner process exists
  3. Point this instance at a different task-store directory to avoid contention

Example fix

// before: parallel instances contending on the same store
codewhale --task-store ~/.codewhale/tasks &
codewhale --task-store ~/.codewhale/tasks &  // busy
// after: isolate per session
codewhale --task-store /tmp/cw-tasks-$SESSION_ID
Defensive patterns

Strategy: retry

Validate before calling

if store_lock_is_held(&path)? { eprintln!("store busy; retry later"); return Ok(()); }

Type guard

fn store_available(path: &Path) -> bool {
    RuntimeProcessOwnerLock::try_acquire_file(path, true).map(|o| o.is_some()).unwrap_or(false)
}

Try / catch

let mut attempts = 0;
loop {
    match manager.load_state().await {
        Err(e) if e.to_string().contains("Task store is busy") && attempts < 5 => {
            attempts += 1;
            tokio::time::sleep(Duration::from_millis(100 * attempts)).await;
        }
        other => break other?,
    }
}

Prevention

When it happens

Trigger: Calling any state-loading operation on the task store while another process holds RuntimeProcessOwnerLock for the whole backoff window (deadline exceeded).

Common situations: 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.

Understand the failure class

Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.

Related errors


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

Appendix: source

Thrown at crates/tui/src/task_manager.rs:2967

    /// Acquire the cross-process task-store lock.
    ///
    /// Polls with exponential backoff (5ms → 50ms) instead of a flat 5ms
    /// interval: under contention the old shape woke ~200×/s for up to its
    /// whole five-second deadline (#6211 R7c). The deadline and the busy
    /// error are unchanged. What this does not do: it does not add the
    /// in-process mutex the issue also suggested — in-process contenders
    /// just back off against the same file lock.
    async fn lock_store(&self) -> Result<RuntimeProcessOwnerLock> {
        let path = self.cfg.data_dir.join("task-store.lock");
        let deadline = Instant::now() + Duration::from_secs(5);
        let mut wait = Duration::from_millis(5);
        loop {
            if let Some(owner) = RuntimeProcessOwnerLock::try_acquire_file(&path, true)? {
                return Ok(owner);
            }
            if Instant::now() >= deadline {
                bail!("Task store is busy; state is unavailable");
            }
            sleep(wait).await;
            wait = (wait * 2).min(Duration::from_millis(50));
        }
    }

    fn refresh_locked(&self, state: &mut ManagerState) -> Result<()> {
        let loaded = load_state(&self.tasks_dir, &self.queue_path)?;
        state.tasks = loaded.tasks;
        state.queue = loaded.queue;
        for (id, events) in &state.pending_events {
            let task = state
                .tasks
                .get_mut(id)
                .context("Pending task disappeared")?;
            self.require_execution_owner(task)?;
            for event in events {
                self.apply_event_to_task(task, event.clone())?;

View on GitHub (pinned to 73e0f67d83)