Hmbown/CodeWhale · warning

a window change is already in progress

Error message

a window change is already in progress

What it means

start_toggle serializes window pin/toggle operations with an atomic BUSY flag. If compare_exchange(false, true) fails, another window change is still in flight, so the new request is rejected instead of interleaving with it. This protects HostWindow::capture/dispatch ordering from concurrent toggles.

Solutions

  1. Retry after a short delay, once the in-flight change completes (BUSY is released by the worker's BusyGuard).
  2. Debounce/deduplicate toggle keypresses in the UI layer so only one request is in flight.
  3. If toggles consistently stay busy, investigate a wedged window-pin worker thread (check for a missing permit.send or a hang in toggle_pin).
Defensive patterns

Strategy: retry

Validate before calling

// poll before issuing the toggle
if !window_control_is_idle() {
    return; // a change is already in progress
}

Try / catch

match start_toggle() {
    Err(e) if e.to_string().contains("already in progress") => {
        // schedule a retry after the in-flight change completes
        schedule_retry(Duration::from_millis(100));
    }
    Err(e) => report(e),
    Ok(()) => {}
}

Prevention

When it happens

Trigger: Calling start_toggle twice before the first window-pin worker thread completes (the worker clears BUSY via BusyGuard on exit); rapid repeated keybindings or programmatic toggles issued back-to-back.

Common situations: User mashes the pin hotkey; an automation/test fires toggles faster than the worker applies them; a prior toggle's worker thread is stuck waiting on the completion mailbox.

Understand the failure class

Background: Conflicting config options: "cannot be used together" — configuration validation errors across open-source libraries — this error's family across 162 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). 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 73e0f67d83)