Hmbown/CodeWhale · error

125

125

Error message

lane child stdout was not piped

What it means

After spawning the lane child process, run_lane_log_proxy must take ownership of the child's piped stdout to frame and log it. If child.stdout is None, the child was spawned without Stdio::piped() for stdout, so the proxy cannot perform its log-proxy contract; it fails with exit code 125 and writes the failure receipt rather than silently dropping output.

Solutions

  1. Ensure the child is spawned with .stdout(Stdio::piped()) so the proxy can capture and frame output.
  2. Check the spawn code path that builds this Command — both stdout and stderr must be piped for the log proxy.
  3. If using a shared spawn helper, verify it configures piped stdio and is not configured with inherit/null for this lane.
  4. Re-run the lane after fixing the spawn; the exit receipt should record success instead of code 125.

Example fix

// before
let child = cmd.spawn()?; // stdout inherited
// after
let child = cmd
    .stdout(Stdio::piped())
    .stderr(Stdio::piped())
    .spawn()?;
Defensive patterns

Strategy: validation

Validate before calling

// Ensure the spawn pipes both streams before handing the child to the proxy
let mut cmd = build_command();
cmd.stdout(std::process::Stdio::piped())
   .stderr(std::process::Stdio::piped());
let child = cmd.spawn()?;
debug_assert!(child.stdout.is_some(), "lane child stdout must be piped");

Type guard

fn child_streams_piped(child: &std::process::Child) -> bool {
    child.stdout.is_some() && child.stderr.is_some()
}

Try / catch

match run_lane_log_proxy(...) {
    Ok(code) if code == 125 => {
        eprintln!("child stdio was not piped; fix spawn to use Stdio::piped()");
    }
    other => {/* handle normally */},
}

Prevention

When it happens

Trigger: run_lane_log_proxy spawns the command and the resulting Child has stdout == None — the spawn used inherited or null stdout (Stdio::inherit()/null()) instead of piped, so child.stdout.take() returns None.

Common situations: A refactor or configuration path changed the spawn's Stdio setup for stdout; a test helper spawns with inherited stdio; the child command was constructed by shared code that pipes only stderr.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/1180767707da3fd1. Report an issue: GitHub.

Appendix: source

Thrown at crates/lane/src/runtime.rs:481

        Vec::new()
    };

    let mut child_command = Command::new(&command[0]);
    child_command.args(&command[1..]);
    child_command.envs(environment);
    child_command.stdout(Stdio::piped()).stderr(Stdio::piped());
    let mut child = match child_command.spawn() {
        Ok(child) => child,
        Err(error) => {
            return fail(
                anyhow::Error::from(error).context(format!("spawn lane child command {command:?}")),
                127,
            );
        }
    };
    let stdout = match child.stdout.take() {
        Some(stdout) => stdout,
        None => return fail(anyhow::anyhow!("lane child stdout was not piped"), 125),
    };
    let stderr = match child.stderr.take() {
        Some(stderr) => stderr,
        None => return fail(anyhow::anyhow!("lane child stderr was not piped"), 125),
    };
    let stdout_log = log_path.clone();
    let stderr_log = log_path.clone();
    let stdout_thread = thread::spawn(move || stream_child_output(stdout, stdout_log, "stdout"));
    let stderr_thread = thread::spawn(move || stream_child_output(stderr, stderr_log, "stderr"));

    let status = match child.wait() {
        Ok(status) => status,
        Err(error) => {
            return fail(
                anyhow::Error::from(error).context(format!("wait for lane child {command:?}")),
                LANE_PROXY_FAILURE_EXIT_CODE,
            );
        }

View on GitHub (pinned to 73e0f67d83)