{"record":{"id":"d0f87f06101d6587","repo":"sigoden/aichat","slug":"failed-to-send-osc52-sequence","errorCode":null,"errorMessage":"Failed to send OSC52 sequence","messagePattern":"Failed to send OSC52 sequence","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"warning","filePath":"src/utils/clipboard.rs","lineNumber":31,"sourceCode":"        let mut clipboard = CLIPBOARD.lock().unwrap();\n        match clipboard.as_mut() {\n            Some(clipboard) => {\n                clipboard.set_text(text)?;\n                #[cfg(target_os = \"linux\")]\n                std::thread::sleep(std::time::Duration::from_millis(50));\n                Ok(())\n            }\n            None => set_text_osc52(text),\n        }\n    }\n\n    /// Attempts to set text to clipboard with OSC52 escape sequence\n    /// Works in many modern terminals, including over SSH.\n    fn set_text_osc52(text: &str) -> anyhow::Result<()> {\n        let encoded = STANDARD.encode(text);\n        let seq = format!(\"\\x1b]52;c;{encoded}\\x07\");\n        if let Err(e) = std::io::Write::write_all(&mut std::io::stdout(), seq.as_bytes()) {\n            return Err(anyhow::anyhow!(\"Failed to send OSC52 sequence\").context(e));\n        }\n        if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) {\n            return Err(anyhow::anyhow!(\"Failed to flush OSC52 sequence\").context(e));\n        }\n        Ok(())\n    }\n}\n\n#[cfg(any(target_os = \"android\", target_os = \"emscripten\"))]\nmod internal {\n    pub fn set_text(_text: &str) -> anyhow::Result<()> {\n        Err(anyhow::anyhow!(\"No clipboard available\"))\n    }\n}\n\npub fn set_text(text: &str) -> anyhow::Result<()> {\n    internal::set_text(text).context(\"Failed to copy\")\n}","sourceCodeStart":13,"sourceCodeEnd":49,"githubUrl":"https://github.com/sigoden/aichat/blob/82976d349ad97ac9aae0655ad631dace5e2a6385/src/utils/clipboard.rs#L13-L49","documentation":"set_text_osc52 failed while writing the OSC 52 escape sequence (ESC ]52;c;<base64>;BEL) to stdout, so the clipboard could not be set via the terminal. The underlying io::Write error is attached as context.","triggerScenarios":"stdout is closed or unwritable (broken pipe after `cmd | head`), stdout redirected to a file that cannot accept the write, or the process lost its terminal.","commonSituations":"Piping CLI output where downstream consumers exit early (SIGPIPE/EPIPE); running in environments where stdout is a full disk or closed fd; CI logs capturing stdout while the tool tries to copy to clipboard.","solutions":["Check that stdout is an interactive terminal before attempting OSC 52 (e.g. atty/is-terminal) and skip otherwise.","Handle the error gracefully — fall back to another clipboard backend (X11/Wayland/pbcopy) instead of failing.","Avoid closing/redirecting stdout when invoking commands that set the clipboard, or fix upstream broken pipes."],"exampleFix":"// before\nfn set_text(text: &str) -> Result<()> { set_text_osc52(text) }\n// after\nfn set_text(text: &str) -> Result<()> {\n    if !std::io::stdout().is_terminal() { anyhow::bail!(\"no terminal for OSC52\"); }\n    set_text_osc52(text)\n}","handlingStrategy":"fallback","validationCode":"if !std::io::stdout().is_terminal() { /* skip OSC52, use another backend */ }","typeGuard":null,"tryCatchPattern":"match set_text_osc52(text) {\n    Ok(()) => (),\n    Err(e) => fallback_backend(text).with_context(|| format!(\"osc52 failed: {e}\")),\n}","preventionTips":["Detect TTY before writing escape sequences","Chain clipboard backends: OSC52 -> X11/Wayland -> pbcopy, ignoring individual failures","Silently degrade (log-only) when stdout is redirected or piped"],"tags":["clipboard","terminal","io"],"backgroundTag":"broken-pipe","analyzedSha":"82976d349ad97ac9aae0655ad631dace5e2a6385","analyzedAt":"2026-09-09T18:33:06.139Z","contentChangedAt":"2026-09-09T18:33:06.139Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}