Hmbown/CodeWhale · warning · std::io::Error

Cannot open session : its queued input is already open in…

Error message

Cannot open session {session_id}: its queued input is already open in another window, or its previous writes are still finishing ({error})

What it means

Raised when opening a session file requires an exclusive write lease (`fd_lock::RwLock::try_write`) but another process/window already holds it, or a prior writer has not released the descriptor. The original lock error's kind is preserved and its text is embedded in the message so the user knows the session is contended, not corrupted.

Solutions

  1. Close the other window/process holding the session, then retry.
  2. Wait a moment and retry — a finishing writer releases the lock on its own.
  3. Find the lingering process (e.g. `lsof`/`fuser` on the session file) and terminate it before reopening.
  4. Retry with backoff if your tooling opens sessions programmatically.
Defensive patterns

Strategy: retry

Validate before calling

ps_aux = /* check no other process of yours has this session open */;

Try / catch

for attempt in 0..5 {
    match manager.open_session(id) {
        Err(e) if e.to_string().contains("already open in another window") => {
            std::thread::sleep(Duration::from_millis(250 * (attempt + 1)));
        }
        other => { other?; break; }
    }
}

Prevention

When it happens

Trigger: Calling the session-open path while a second window has the same session_id open, or immediately after a crash while the previous writer's file handle/lock is still held.

Common situations: Opening the same session in two terminal windows; a crashed TUI leaving a stale lock briefly; concurrent background write finishing while a resume is attempted.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15). Data as JSON: /api/errors/3612a5899e7c6e79. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/session_manager.rs:2127

    /// resume. A per-write lock is insufficient: the second editor's stale
    /// snapshot would overwrite the first as soon as its write completed.
    pub fn acquire_offline_queue_lease(
        &self,
        session_id: &str,
    ) -> io::Result<std::sync::Arc<OfflineQueueLease>> {
        let session_id = self.validated_session_id(session_id)?.to_string();
        let directory = self.checkpoints_dir();
        fs::create_dir_all(&directory)?;
        let path = directory.join(format!("{session_id}.offline_queue.lock"));
        let file = fs::OpenOptions::new()
            .create(true)
            .truncate(false)
            .read(true)
            .write(true)
            .open(path)?;
        let mut lock = fd_lock::RwLock::new(file);
        let guard = lock.try_write().map_err(|error| {
            io::Error::new(
                error.kind(),
                format!("Cannot open session {session_id}: its queued input is already open in another window, or its previous writes are still finishing ({error})"),
            )
        })?;
        // fd-lock's guard borrows its owner. Retain the underlying descriptor
        // instead so this lease can travel with asynchronous writes. Forgetting
        // this non-owning guard keeps the OS lock held; the final Arc explicitly
        // unlocks in Drop. The OS also releases it when the process crashes.
        std::mem::forget(guard);
        Ok(std::sync::Arc::new(OfflineQueueLease {
            session_id,
            _file: lock.into_inner(),
        }))
    }

    /// Park this session's offline queue (queued + draft messages).
    ///
    /// Queues are keyed per session (`checkpoints/<session_id>.offline_queue.json`)

View on GitHub (pinned to 433685b202)