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

  1. Run the program with stdout attached to a live interactive terminal instead of a closed pipe or redirect.
  2. Check that the parent shell/SSH session is still alive and that stdout is not broken.
  3. Use an alternative clipboard backend (e.g. a native clipboard crate) when no TTY is available.
  4. 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

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


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)