{"record":{"id":"a1f27a737d00c8d0","repo":"Hmbown/CodeWhale","slug":"terminal-clipboard-writer-stopped","errorCode":null,"errorMessage":"terminal clipboard writer stopped","messagePattern":"terminal clipboard writer stopped","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/tui/clipboard.rs","lineNumber":291,"sourceCode":"            })\n            .context(\"spawn terminal clipboard writer\")?;\n        Ok(Self {\n            request_tx,\n            completion_rx,\n        })\n    }\n\n    fn enqueue(&self, text: &str, in_tmux: bool) -> Result<()> {\n        let request = TerminalClipboardWriteRequest {\n            text: text.to_string(),\n            in_tmux,\n        };\n        self.request_tx.try_send(request).map_err(|err| match err {\n            std::sync::mpsc::TrySendError::Full(_) => {\n                anyhow::anyhow!(\"another terminal clipboard write is still queued\")\n            }\n            std::sync::mpsc::TrySendError::Disconnected(_) => {\n                anyhow::anyhow!(\"terminal clipboard writer stopped\")\n            }\n        })\n    }\n\n    fn poll_completion(&self) -> Option<TerminalClipboardWriteCompletion> {\n        self.completion_rx.try_recv().ok()\n    }\n}\n\n/// Clipboard reader/writer helper.\npub struct ClipboardHandler {\n    terminal_context: TerminalClipboardContext,\n    terminal_writer: Option<TerminalClipboardWriter>,\n    #[cfg(all(target_os = \"linux\", not(target_env = \"ohos\"), not(test)))]\n    primary: Option<primary::PrimarySelection>,\n    #[cfg(test)]\n    primary_enabled: bool,\n    #[cfg(test)]","sourceCodeStart":273,"sourceCodeEnd":309,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/tui/clipboard.rs#L273-L309","documentation":"The terminal clipboard writer's background thread has exited, dropping its side of the std::sync::mpsc channel, so a new clipboard write request cannot be queued. enqueue() surfaces the Disconnected variant of try_send as this error to distinguish a dead writer from a merely busy one.","triggerScenarios":"Calling enqueue_terminal_write (via write_text with ClipboardWriteOrder::TerminalClientOnly) after the writer thread has terminated — typically because the worker saw a shutdown signal, panicked, or the TUI terminal context was torn down while a clipboard write was still attempted.","commonSituations":"Clipboard write racing app shutdown or terminal teardown; writer thread panic from an invalid escape-sequence target; a plugin or test harness keeping a stale Clipboard handle after the terminal backend was dropped.","solutions":["Recreate or reinitialize the terminal clipboard backend/writer thread before retrying the write","Fall back to a direct clipboard write path (wlcopy / OSC52 handled elsewhere) instead of the terminal writer","Discard the stale Clipboard handle and obtain a fresh one from the live terminal context"],"exampleFix":"// before\nif let Err(err) = clipboard.write_text(text) {\n    eprintln!(\"copy failed: {err}\");\n}\n// after\nif let Err(err) = clipboard.write_text(text) {\n    if err.to_string().contains(\"writer stopped\") {\n        clipboard = Clipboard::new(&terminal_context); // rebuild writer\n    }\n}","handlingStrategy":"try-catch","validationCode":"// Best-effort liveness probe: a disconnected channel fails fast.\nfn writer_alive(tx: &std::sync::mpsc::Sender<Request>) -> bool {\n    tx.send(Request::Ping).is_ok() // or check an AtomicBool liveness flag\n}","typeGuard":"fn is_disconnected(err: &anyhow::Error) -> bool {\n    err.to_string().contains(\"writer stopped\")\n}","tryCatchPattern":"match clipboard.write_text(text) {\n    Ok(()) => {},\n    Err(e) if is_disconnected(&e) => rebuild_clipboard_writer_and_retry(),\n    Err(e) => log::warn!(\"clipboard unavailable: {e}\"),\n}","preventionTips":["Keep a liveness flag on the writer thread and check it before enqueueing","Never attempt clipboard writes during/after terminal teardown","Handle writer-thread panics so the channel stays usable or the handle is invalidated"],"tags":["clipboard","terminal","channel-disconnected","async"],"backgroundTag":"broken-pipe","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}