Hmbown/CodeWhale · error

lane child stderr was not piped

Error message

lane child stderr was not piped

What it means

The sibling guard for stderr in run_lane_log_proxy: after spawning the lane child with .stderr(Stdio::piped()), child.stderr.take() returned None, so the proxy cannot stream the child's stderr into the lane log. It appends a lane_proxy_failure event and publishes exit receipt 125. Like the stdout variant it is an internal invariant guard that should be unreachable with the current spawn configuration.

Solutions

  1. Confirm .stderr(Stdio::piped()) is still chained before child_command.spawn() at crates/lane/src/runtime.rs:469
  2. If the Command is assembled in another function, keep the piped-stdio configuration at the spawn site and assert it
  3. Add/keep a regression test that exercises run_lane_log_proxy end-to-end so any stdio change is caught
  4. Read the lane log's lane_proxy_error entry to confirm the 125 exit path

Example fix

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

Strategy: validation

Validate before calling

let mut cmd = Command::new(&command[0]);
cmd.args(&command[1..]).stdout(Stdio::piped()).stderr(Stdio::piped());
let mut child = cmd.spawn().context("spawn lane child")?;
debug_assert!(child.stderr.is_some());

Type guard

fn piped_stderr(child: &Child) -> Option<&ChildStderr> { child.stderr.as_ref() }

Try / catch

let stderr = match child.stderr.take() {
    Some(s) => s,
    None => return fail(anyhow!("lane child stderr was not piped"), 125),
};

Prevention

When it happens

Trigger: The .stderr(Stdio::piped()) configuration is missing or removed before spawn; the Child was constructed by code that configured stderr as inherit/null; a platform quirk yields a successful spawn without a stderr pipe.

Common situations: Refactors that split child-command construction; helpers that return a Child rebuilt without stderr piped; downstream merges that touch the spawn block at runtime.rs:466-478.

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@433685b202 (2026-09-15). Data as JSON: /api/errors/7962137ed6aec587. Report an issue: GitHub.

Appendix: source

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

    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,
            );
        }
    };
    let stdout_result = stdout_thread
        .join()
        .map_err(|_| anyhow::anyhow!("lane proxy stdout logger panicked"))?;

View on GitHub (pinned to 433685b202)