Hmbown/CodeWhale · error

window worker panicked

Error message

window worker panicked

What it means

The window-pin worker runs toggle_pin inside std::panic::catch_unwind; if the closure panics, the panic is converted into this Err so the result can still be dispatched back to the app instead of killing the worker silently. It reports that the window toggle failed due to an internal panic.

Solutions

  1. Check the app log for the panic backtrace to find the actual panicking site in toggle_pin.
  2. Reproduce with the current window layout (single window, minimized/fullscreen windows) and fix the unwrapping/indexing in toggle_pin.
  3. Retry the toggle; it is usually safe to re-issue since the worker state is reset.
  4. File a bug with the panic message if it reproduces on a supported platform.
Defensive patterns

Strategy: try-catch

Try / catch

match start_toggle() {
    Err(e) if e.to_string().contains("window worker panicked") => {
        log_panic_backtrace(e);
        show_user_friendly_window_error();
    }
    Err(e) => report(e),
    Ok(()) => {}
}

Prevention

When it happens

Trigger: toggle_pin panicking while manipulating host windows (e.g. unwrapping on a window API result, index out of bounds on window lists, or OS API returning an unexpected value).

Common situations: OS window-manager state changed underneath the TUI (window closed concurrently), platform quirks in the host window API, or a bug in toggle_pin reached by an unusual window configuration (e.g. only one window, minimized windows).

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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

Appendix: source

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

        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(())
    }

    /// The host window the user sees, if one can be resolved.
    ///
    /// Classic console hosts (conhost, legacy cmd windows) hand back a real
    /// window from `GetConsoleWindow`. ConPTY hosts (Windows Terminal, VS
    /// Code integrated terminal) have no classic console window, so first
    /// check the foreground window (the user just right-clicked inside the
    /// host, so it is almost certainly the host window) and then fall back
    /// to the parent process chain.

View on GitHub (pinned to 73e0f67d83)