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

  1. Re-run the command — transient pipe/thread failures may not reproduce.
  2. Run the command directly (bypass rtk) to verify the child itself is healthy; if so, file an rtk bug with the repro.
  3. Raise fd/memory limits (`ulimit -n`) if resource exhaustion is suspected.
  4. 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

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)