Hmbown/CodeWhale · error · anyhow::Error
Failed to write to {program}: {e}
Error message
Failed to write to {program}: {e} What it means
write_all to wl-copy's piped stdin failed. Nearly always EPIPE: wl-copy spawned but exited immediately (no reachable Wayland compositor, stale WAYLAND_DISPLAY, clipboard daemon error), closing the pipe before the payload was written. Distinct from wl-copy's own non-zero exit, which is reported separately after wait().
Source
Thrown at crates/tui/src/tui/clipboard.rs:588
.map_err(|e| anyhow::anyhow!("Failed to run {program}: {e}"))?;
if !output.status.success() {
bail!("{program} exited with {}", output.status);
}
String::from_utf8(output.stdout).context("wl-paste returned non-UTF-8 text")
}
#[cfg(all(target_os = "linux", not(target_env = "ohos"), not(test)))]
fn write_text_with_wlcopy_using_argv(program: &str, text: &str) -> Result<()> {
let mut child = Command::new(program)
.stdin(Stdio::piped())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.map_err(|e| anyhow::anyhow!("Failed to run {program}: {e}"))?;
if let Some(mut stdin) = child.stdin.take() {
stdin
.write_all(text.as_bytes())
.map_err(|e| anyhow::anyhow!("Failed to write to {program}: {e}"))?;
}
// stdin is dropped here, closing the pipe so wl-copy flushes.
let status = child
.wait()
.map_err(|e| anyhow::anyhow!("Failed to wait on {program}: {e}"))?;
if !status.success() {
bail!("{program} exited with {status}");
}
Ok(())
}
#[cfg(not(test))]
fn write_text_to_terminal_client(text: &str, in_tmux: bool) -> Result<()> {
if in_tmux {
return write_text_with_tmux(text);
}
write_text_with_osc52(text)
}View on GitHub (pinned to 0c42157ee5)
Solutions
- Verify a live Wayland session in the same shell: `echo $WAYLAND_DISPLAY` then `echo hi | wl-copy`
- unset WAYLAND_DISPLAY if the session is not actually Wayland so the OSC 52 fallback wins cleanly
- Restart the compositor/session when the socket is stale
Example fix
# before export WAYLAND_DISPLAY=wayland-0 # stale, compositor gone codewhale tui # copy -> Failed to write to wl-copy: broken pipe # after unset WAYLAND_DISPLAY codewhale tui # falls back to OSC 52 / tmux
Defensive patterns
Strategy: fallback
Validate before calling
// Rust - probe the Wayland clipboard socket liveness cheaply before large writes
fn wayland_session_alive() -> bool {
std::env::var("WAYLAND_DISPLAY").map(|d| {
let runtime = std::env::var("XDG_RUNTIME_DIR").unwrap_or_default();
!runtime.is_empty() && std::path::Path::new(&runtime).join(&d).exists()
}).unwrap_or(false)
} Try / catch
match write_text_with_wlcopy(text) {
Err(e) if e.chain().any(|c| c.to_string().contains("broken pipe")) => {
unset_env_and_use_osc52(); // compositor gone: fall through, never retry wl-copy
}
r => r,
} Prevention
- Validate WAYLAND_DISPLAY points at a live socket after SSH/compositor changes
- Treat EPIPE from clipboard helpers as 'transport refused', not a transient error
- Chain transports so a dead compositor degrades to OSC 52/tmux automatically
When it happens
Trigger: wl-copy exits at startup because WAYLAND_DISPLAY points at a dead compositor socket or the session bus is unavailable; stdin.write_all then returns BrokenPipe.
Common situations: SSH sessions inheriting a stale WAYLAND_DISPLAY; compositor crash; running wl-copy on X11-only systems.
Related errors
- Failed to run {program}: {e}
- {program} exited with {}
- {program} exited with {status}
- Failed to write to {label}: {e}
- Failed to wait on {program}: {e}
AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20).
Data as JSON: /api/errors/b0689240a4f0643c.
Report an issue: GitHub.