rtk-ai/rtk · error
stderr streaming thread panicked
Error message
stderr streaming thread panicked
What it means
This is the stderr counterpart of the stdout streaming panic: the child's stderr is drained by its own thread, and after `wait()` the JoinHandle is joined at src/main.rs:3178. A panicked stderr reader thread is mapped to `anyhow!("stderr streaming thread panicked")`. The proxied command may have actually completed; the failure is in rtk's internal stderr collection.
Solutions
- Re-run the command — transient pipe/thread failures may not reproduce.
- Run the command directly (bypass rtk) to verify the child itself is healthy; if so, file an rtk bug with the repro.
- Raise fd/memory limits (`ulimit -n`) if resource exhaustion is suspected.
- Upgrade rtk; internal streaming-thread panics are bugs to be fixed in the proxy implementation.
Defensive patterns
Strategy: retry
Try / catch
rtk proxy <cmd> || <cmd> # rerun directly if rtk's stderr thread panics
Prevention
- Avoid children that hijack their own stderr fd.
- Watch memory/fd limits when streaming very verbose output.
- Treat recurrence as an rtk bug: capture the repro command and report it.
When it happens
Trigger: The stderr reader thread panics while consuming the child's piped stderr (pipe read error or internal unwrap failure), causing `stderr_handle.join()` to return Err, which main.rs converts into this error after the child has been waited on.
Common situations: Very chatty child processes producing huge stderr under memory pressure; child processes that manipulate their own stderr fd; fd-limit exhaustion causing pipe reads to fail inside the thread.
Related errors
AI-assisted analysis of rtk-ai/rtk@0924356b4c (2026-09-19).
Data as JSON: /api/errors/15eaab16a41e2e8c.
Report an issue: GitHub.
Appendix: source
Thrown at src/main.rs:3178
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)
}
Commands::Trust { list, yes } => {
hooks::trust::run_trust(list, yes)?;View on GitHub (pinned to 0924356b4c)