rtk-ai/rtk · error
stdout streaming thread panicked
Error message
stdout streaming thread panicked
What it means
In rtk's unfiltered proxy path, stdout of the spawned child is drained by a dedicated thread that streams bytes into a buffer. After `child.wait()` completes, main.rs joins that thread's JoinHandle; if the thread panicked (the handle returns Err), it maps the panic to `anyhow!("stdout streaming thread panicked")`. This indicates an internal reader-thread failure, not a failure of the proxied command itself.
Solutions
- Re-run the command with `rtk proxy` retried once — transient pipe errors often disappear.
- Run the underlying command directly (outside rtk) to confirm the command itself works; if it does, report the rtk bug with the repro command.
- Check system resource limits (`ulimit -n`, memory) that could break pipe reads in the streaming thread.
- Update rtk to the latest version, as streaming-thread panics are typically internal bugs fixed upstream.
Defensive patterns
Strategy: retry
Try / catch
rtk proxy <cmd> || {
# fall back to running the command directly
<cmd>
} Prevention
- Keep system fd/memory limits healthy for long streaming pipelines.
- Avoid child commands that close or rewire their own stdout.
- On recurrence, bypass with `rtk proxy` semantics removed (run raw) and report the bug upstream.
When it happens
Trigger: The stdout reader thread spawned in the proxy execution path panics while reading from the child's piped stdout (e.g. I/O read error on the pipe or an unwrap inside the thread), so `stdout_handle.join()` at src/main.rs:3175 returns Err and is converted into this anyhow error.
Common situations: Child process closes/steals its own stdout pipe abnormally; OS-level pipe errors under resource exhaustion (fd limits, OOM killer hitting the thread); a bug in the reader thread's unwrapping logic on unusual byte streams.
Related errors
AI-assisted analysis of rtk-ai/rtk@0924356b4c (2026-09-19).
Data as JSON: /api/errors/d43c6c66c8b6acc1.
Report an issue: GitHub.
Appendix: source
Thrown at src/main.rs:3175
}
let mut err = std::io::stderr().lock();
err.write_all(&buf[..count])?;
err.flush()?;
}
Ok(captured)
});
let status = child
.0
.take()
.context("Child process missing")?
.wait()
.context(format!("Failed waiting for command: {}", cmd_name))?;
let stdout_bytes = stdout_handle
.join()
.map_err(|_| anyhow::anyhow!("stdout streaming thread panicked"))??;
let stderr_bytes = stderr_handle
.join()
.map_err(|_| anyhow::anyhow!("stderr streaming thread panicked"))??;
let stdout = core::utils::decode_process_output(&stdout_bytes);
let stderr = core::utils::decode_process_output(&stderr_bytes);
let full_output = format!("{}{}", stdout, stderr);
// Track usage (input = output since no filtering)
timer.track(
&format!("{} {}", cmd_name, cmd_args.join(" ")),
&format!("rtk proxy {} {}", cmd_name, cmd_args.join(" ")),
&full_output,
&full_output,
);
core::utils::exit_code_from_status(&status, &cmd_name)
}View on GitHub (pinned to 0924356b4c)