rtk-ai/rtk · error
Failed to relay stdin: {}
Error message
Failed to relay stdin: {} What it means
In passthrough mode (`rtk pipe --passthrough`), rtk does a byte-for-byte std::io::copy from stdin to stdout; any IO error on either end surfaces as `Failed to relay stdin: {e}`. The dominant cause is EPIPE: the downstream consumer (head, grep -m1, an early-exiting agent reader) closed the pipe before rtk finished writing.
Source
Thrown at src/cmds/system/pipe_cmd.rs:249
identity_filter
}
fn identity_filter(input: &str) -> String {
input.to_string()
}
fn apply_filter(filter_fn: fn(&str) -> String, input: &str) -> String {
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| filter_fn(input)))
.unwrap_or_else(|_| {
eprintln!("[rtk] warning: filter panicked — passing through raw output");
input.to_string()
})
}
pub fn run(filter_name: Option<&str>, passthrough: bool) -> Result<()> {
if passthrough {
std::io::copy(&mut std::io::stdin(), &mut std::io::stdout())
.map_err(|e| anyhow::anyhow!("Failed to relay stdin: {}", e))?;
return Ok(());
}
let mut buf = String::new();
std::io::stdin()
.take((RAW_CAP + 1) as u64)
.read_to_string(&mut buf)
.map_err(|e| anyhow::anyhow!("Failed to read stdin: {}", e))?;
if buf.len() > RAW_CAP {
anyhow::bail!("stdin exceeds {} byte limit", RAW_CAP);
}
let filter_fn = match filter_name {
Some(name) => resolve_filter(name).ok_or_else(|| {
anyhow::anyhow!(
"Unknown filter '{}'. Available: cargo-test, pytest, go-test, go-build, \
tsc, vitest, grep, rg, find, fd, git-log, git-diff, git-status, \
log, mypy, ruff-check, ruff-format, prettier, phpunit, pest, \View on GitHub (pinned to d977e1c316)
Solutions
- Make the downstream consumer read until EOF, or write to a file instead: `rtk pipe --passthrough < in > out.txt`
- Move early-exiting consumers (head, grep -m1) upstream of rtk pipe, or replace `cmd | rtk pipe --passthrough | head` with `head input | rtk pipe --passthrough`
- If you wrap rtk in code, treat EPIPE/BrokenPipeError as success — the bytes written before close were delivered
Example fix
# before: head closes the pipe -> "Failed to relay stdin: Broken pipe" rtk pipe --passthrough < huge.log | head -5 # after: consume to EOF (file) or truncate before rtk rtk pipe --passthrough < huge.log > excerpt.txt head -c 1M huge.log | rtk pipe --passthrough
Defensive patterns
Strategy: try-catch
Try / catch
rust (wrapping rtk pipe --passthrough):
use std::io::ErrorKind;
match std::io::copy(&mut std::io::stdin(), &mut std::io::stdout()) {
Err(e) if e.kind() == ErrorKind::BrokenPipe => Ok(()), // consumer done: not an error
other => other,
}
bash equivalent: `rtk pipe --passthrough < in 2>/dev/null | head -5; test ${PIPESTATUS[0]:-0} -eq 0 || true` — or simply ensure the consumer reads to EOF. Prevention
- Never place early-exiting consumers (head, sed q, grep -m1) downstream of rtk pipe
- Write to a file when you need the full stream: `rtk pipe --passthrough < in > out.txt`
- Treat EPIPE after rtk as success in wrappers — bytes before the close were delivered
- Keep truncation upstream: `head -c N input | rtk pipe --passthrough`
When it happens
Trigger: `rtk pipe --passthrough < big.log | head -5`; piping into `grep -m1 pattern` which exits after the first match; a downstream process crashing mid-stream; an upstream producer closing stdin early; SSH session drop mid-relay.
Common situations: Unix pipelines that truncate output after rtk (head/sed q/awk exit); agent harnesses that read N bytes then close the fd; piping into tools that exit on first match; flaky remote sessions.
Related errors
- Failed to read stdin: {}
- stdin exceeds {} byte limit
- Unknown filter '{}'. Available: cargo-test, pytest, go-test,
- hook stdin exceeds {} byte limit
AI-assisted analysis of rtk-ai/rtk@d977e1c316 (2026-08-16).
Data as JSON: /api/errors/bad2ea4e8522b017.
Report an issue: GitHub.