sigoden/aichat · error · anyhow::Error
Failed to flush OSC52 sequence
Error message
Failed to flush OSC52 sequence
What it means
This error is raised by `set_text_osc52` (via the platform `internal::set_text` behind the public `set_text`) after writing an OSC 52 escape sequence to stdout, when `std::io::stdout().flush()` fails. OSC 52 copy-to-clipboard works by emitting an escape sequence that the terminal itself must consume, so flushing stdout is what actually delivers the sequence. A flush failure means the process could not push the bytes out to the terminal (typically because stdout is closed, broken, or blocked).
Solutions
- Run the program with stdout attached to a live interactive terminal instead of a closed pipe or redirect.
- Check that the parent shell/SSH session is still alive and that stdout is not broken.
- Use an alternative clipboard backend (e.g. a native clipboard crate) when no TTY is available.
- Catch the error and warn instead of failing the whole command, since clipboard copy is usually non-essential.
Example fix
// before
let seq = format!("\x1b]52;c;{encoded}\x07");
std::io::Write::write_all(&mut std::io::stdout(), seq.as_bytes())?;
std::io::Write::flush(&mut std::io::stdout())?;
// after
if let Err(e) = std::io::Write::write_all(&mut std::io::stdout(), seq.as_bytes())
.and_then(|_| std::io::Write::flush(&mut std::io::stdout()))
{
eprintln!("warning: could not copy to clipboard via OSC52: {e}");
} Defensive patterns
Strategy: try-catch
Validate before calling
use std::io::IsTerminal;
fn osc52_viable() -> bool {
std::io::stdout().is_terminal()
} Type guard
fn stdout_is_usable() -> bool {
std::io::stdout().is_terminal()
} Try / catch
match set_text(text) {
Ok(()) => println!("Copied to clipboard"),
Err(e) if e.to_string().contains("flush OSC52") =>
eprintln!("warning: terminal did not accept clipboard data: {e}"),
Err(e) => return Err(e.into()),
} Prevention
- Check stdout is a TTY (io::IsTerminal) before using the OSC 52 backend.
- Fall back to a native clipboard backend when stdout is redirected.
- Treat clipboard copy as best-effort: warn, don't abort the command.
- Avoid piping the CLI into commands that close stdout early when copying.
When it happens
Trigger: Calling `set_text` on a platform using the OSC 52 path when `stdout().flush()` returns an error: stdout is a closed pipe, the terminal was killed mid-write, or stdout is redirected to a file/socket that rejects the write.
Common situations: Running the CLI inside `head`/`grep` pipelines where stdout closes early; SSH sessions with a dying connection; output redirected to a file instead of a TTY so no terminal is there to consume OSC 52.
Related errors
- Failed to send OSC52 sequence
- No chat response to copy
- Invalid wrap value
- No clipboard available
- Interrupted
AI-assisted analysis of sigoden/aichat@82976d349a (2026-09-09).
Data as JSON: /api/errors/b39003b4dc1f8540.
Report an issue: GitHub.
Appendix: source
Thrown at src/utils/clipboard.rs:34
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)