{"record":{"id":"03db4d41d244b3c0","repo":"Hmbown/CodeWhale","slug":"a-window-change-is-already-in-progress","errorCode":null,"errorMessage":"a window change is already in progress","messagePattern":"a window change is already in progress","errorType":"error_code","errorClass":"anyhow::Error","httpStatus":null,"severity":"warning","filePath":"crates/tui/src/tui/window_control.rs","lineNumber":190,"sourceCode":"            }\n            if Instant::now() >= deadline {\n                return Ok(false);\n            }\n            std::thread::sleep(Duration::from_millis(15));\n        }\n    }\n\n    pub(super) fn start_toggle(\n        completion_tx: Option<tokio::sync::mpsc::Sender<crate::tui::app::DispatchApplyFn>>,\n    ) -> Result<()> {\n        // Reserve delivery before changing the window. A headless test App has\n        // no mailbox and cannot accidentally manipulate its real terminal.\n        let permit = completion_tx\n            .context(\"window completion mailbox is unavailable\")?\n            .try_reserve_owned()\n            .context(\"window completion mailbox is full or closed\")?;\n        BUSY.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)\n            .map_err(|_| anyhow::anyhow!(\"a window change is already in progress\"))?;\n        let busy = BusyGuard;\n        // Foreground selection belongs to the user's action, before dispatch;\n        // the worker must not pick a different window after focus changes.\n        let host = HostWindow::capture()?;\n        std::thread::Builder::new()\n            .name(\"window-pin\".into())\n            .spawn(move || {\n                let result = std::panic::catch_unwind(|| toggle_pin(host))\n                    .unwrap_or_else(|_| Err(anyhow::anyhow!(\"window worker panicked\")));\n                let apply: crate::tui::app::DispatchApplyFn = Box::new(move |app, _, _| {\n                    super::show_result(app, result);\n                    Ok(())\n                });\n                permit.send(apply);\n                drop(busy);\n            })?;\n        Ok(())\n    }","sourceCodeStart":172,"sourceCodeEnd":208,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/crates/tui/src/tui/window_control.rs#L172-L208","documentation":"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.","triggerScenarios":"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.","commonSituations":"Impatient double-click on the window-pin control; automation scripts issuing rapid toggles; a hung window worker keeping BUSY set.","solutions":["Wait for the in-progress window change to complete and retry","Debounce the UI action so repeated clicks are ignored while busy","If BUSY appears stuck, investigate the window-pin worker thread for a stall in permit.send"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"match start_toggle(...) {\n    Err(e) if e.to_string().contains(\"already in progress\") => {\n        std::thread::sleep(Duration::from_millis(150));\n        start_toggle(...)?; // single retry after in-flight change completes\n    }\n    other => other?,\n}","preventionTips":["Debounce window-pin UI clicks","Track an is-busy flag in your own automation","Investigate worker stalls if busy never clears"],"tags":["concurrency","window","busy"],"backgroundTag":"invalid-state-transition","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-22T01:17:13.364Z"}