Hmbown/CodeWhale · warning · anyhow::Error

a window change is already in progress

Error message

a window change is already in progress

What it means

Thrown by start_toggle when the window-control BUSY atomic flag is already true, meaning another window toggle (pin) operation is currently in flight. The module serializes window changes: only one toggle may run at a time, enforced by a compare_exchange on BUSY, so a second concurrent request is rejected instead of racing the worker thread.

Solutions

  1. Wait for the in-progress window change to complete and retry
  2. Debounce the UI action so repeated clicks are ignored while busy
  3. If BUSY appears stuck, investigate the window-pin worker thread for a stall in permit.send
Defensive patterns

Strategy: retry

Try / catch

match start_toggle(...) {
    Err(e) if e.to_string().contains("already in progress") => {
        std::thread::sleep(Duration::from_millis(150));
        start_toggle(...)?; // single retry after in-flight change completes
    }
    other => other?,
}

Prevention

When it happens

Trigger: Clicking a pin/toggle control twice quickly before the first worker finishes; programmatically invoking start_toggle concurrently from two threads; a previous toggle whose BusyGuard was never dropped due to a stall in permit delivery.

Common situations: Impatient double-click on the window-pin control; automation scripts issuing rapid toggles; a hung window worker keeping BUSY set.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

Thrown at crates/tui/src/tui/window_control.rs:190

            }
            if Instant::now() >= deadline {
                return Ok(false);
            }
            std::thread::sleep(Duration::from_millis(15));
        }
    }

    pub(super) fn start_toggle(
        completion_tx: Option<tokio::sync::mpsc::Sender<crate::tui::app::DispatchApplyFn>>,
    ) -> Result<()> {
        // Reserve delivery before changing the window. A headless test App has
        // no mailbox and cannot accidentally manipulate its real terminal.
        let permit = completion_tx
            .context("window completion mailbox is unavailable")?
            .try_reserve_owned()
            .context("window completion mailbox is full or closed")?;
        BUSY.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
            .map_err(|_| anyhow::anyhow!("a window change is already in progress"))?;
        let busy = BusyGuard;
        // Foreground selection belongs to the user's action, before dispatch;
        // the worker must not pick a different window after focus changes.
        let host = HostWindow::capture()?;
        std::thread::Builder::new()
            .name("window-pin".into())
            .spawn(move || {
                let result = std::panic::catch_unwind(|| toggle_pin(host))
                    .unwrap_or_else(|_| Err(anyhow::anyhow!("window worker panicked")));
                let apply: crate::tui::app::DispatchApplyFn = Box::new(move |app, _, _| {
                    super::show_result(app, result);
                    Ok(())
                });
                permit.send(apply);
                drop(busy);
            })?;
        Ok(())
    }

View on GitHub (pinned to 433685b202)