Hmbown/CodeWhale · error · io::Error

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

Error message

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

What it means

Opening a session requires an exclusive write lease (fd_lock::RwLock::try_write) on its queued-input file. When another window already holds the lease, or a previous write has not finished releasing it, try_write fails and the manager rewraps the error with a message naming the session id and the underlying cause.

Solutions

  1. Close the other window/process that has this session open, then retry opening it
  2. Wait briefly and retry — a finishing write releases the lock on its own
  3. If a stale process is dead but the lock persists, kill the leftover Codewhale process (check boot-owner records / ps) and reopen
  4. As a last resort, remove the orphaned lock by ensuring no process holds the file descriptor, then reopen the session
Defensive patterns

Strategy: retry

Try / catch

for attempt in 0..3 {
    match manager.open_session(&session_id) {
        Err(e) if e.to_string().contains("already open in another window") && attempt < 2 => {
            std::thread::sleep(Duration::from_millis(500));
        }
        Err(e) if e.to_string().contains("already open in another window") => {
            eprintln!("session {session_id} is locked elsewhere; close it and retry");
            return;
        }
        other => { other?; break; }
    }
}

Prevention

When it happens

Trigger: Calling the session-open/lease path while the same session's input file is write-locked by a second process/window, or immediately after a crashed/pending write that has not yet released the fd lock.

Common situations: The same session opened in two terminal windows; a previous Codewhale process still shutting down or hung while holding the lease; an async writer from the last run that never released the guard.

Related errors


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

Appendix: source

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

    /// 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 73e0f67d83)