sigoden/aichat · warning · anyhow::Error
Failed to send OSC52 sequence
Error message
Failed to send OSC52 sequence
What it means
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.
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.
Example fix
// before
fn set_text(text: &str) -> Result<()> { set_text_osc52(text) }
// after
fn set_text(text: &str) -> Result<()> {
if !std::io::stdout().is_terminal() { anyhow::bail!("no terminal for OSC52"); }
set_text_osc52(text)
} Defensive patterns
Strategy: fallback
Validate before calling
if !std::io::stdout().is_terminal() { /* skip OSC52, use another backend */ } Try / catch
match set_text_osc52(text) {
Ok(()) => (),
Err(e) => fallback_backend(text).with_context(|| format!("osc52 failed: {e}")),
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Failed to flush OSC52 sequence
- No chat response to copy
- Failed to write to ' ', No parent path
- Invalid wrap value
- No clipboard available
AI-assisted analysis of sigoden/aichat@82976d349a (2026-09-09).
Data as JSON: /api/errors/d0f87f06101d6587.
Report an issue: GitHub.
Appendix: source
Thrown at src/utils/clipboard.rs:31
let mut clipboard = CLIPBOARD.lock().unwrap();
match clipboard.as_mut() {
Some(clipboard) => {
clipboard.set_text(text)?;
#[cfg(target_os = "linux")]
std::thread::sleep(std::time::Duration::from_millis(50));
Ok(())
}
None => set_text_osc52(text),
}
}
/// Attempts to set text to clipboard with OSC52 escape sequence
/// Works in many modern terminals, including over SSH.
fn set_text_osc52(text: &str) -> anyhow::Result<()> {
let encoded = STANDARD.encode(text);
let seq = format!("\x1b]52;c;{encoded}\x07");
if let Err(e) = std::io::Write::write_all(&mut std::io::stdout(), seq.as_bytes()) {
return Err(anyhow::anyhow!("Failed to send OSC52 sequence").context(e));
}
if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) {
return Err(anyhow::anyhow!("Failed to flush OSC52 sequence").context(e));
}
Ok(())
}
}
#[cfg(any(target_os = "android", target_os = "emscripten"))]
mod internal {
pub fn set_text(_text: &str) -> anyhow::Result<()> {
Err(anyhow::anyhow!("No clipboard available"))
}
}
pub fn set_text(text: &str) -> anyhow::Result<()> {
internal::set_text(text).context("Failed to copy")
}View on GitHub (pinned to 82976d349a)