Hmbown/CodeWhale · error

lane child stdout was not piped

Error message

lane child stdout was not piped

What it means

run_lane_log_proxy spawns the lane child with stdout(Stdio::piped()) (runtime.rs:469) and then calls child.stdout.take(); this error fires when that take() returns None, i.e. no piped stdout handle is available even though the command spawned. The proxy needs the pipe to frame the child's stdout as NDJSON into the lane log, so without it no receipt-worthy log can be produced and it fails with exit code 125. It is an internal invariant guard: with the current spawn configuration it should be unreachable.

Solutions

  1. Verify child_command still calls .stdout(Stdio::piped()) (and .stderr(Stdio::piped())) before spawn in run_lane_log_proxy (crates/lane/src/runtime.rs:469)
  2. If the command is built elsewhere, make the helper return the configured Command unchanged or re-assert piped stdio at the spawn site
  3. Add a unit test asserting the spawn setup so a stdio regression fails at test time
  4. Check the lane log for the appended lane_proxy_error event to confirm which exit path (125) was taken

Example fix

// before
let mut child_command = Command::new(&command[0]);
child_command.args(&command[1..]);
let mut child = child_command.spawn()?;
// after
let mut child_command = Command::new(&command[0]);
child_command.args(&command[1..]);
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());
assert_piped(&cmd); // keep piped stdio config adjacent to spawn; never build the Command elsewhere

Type guard

fn piped_stdout(child: &Child) -> Option<&ChildStdout> { child.stdout.as_ref() }

Try / catch

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

Prevention

When it happens

Trigger: A refactor or platform-specific Command wrapper changes/removes the .stdout(Stdio::piped()) call before spawn; a child_command is constructed elsewhere and passed in without piped stdout; or an OS-level failure left the Child struct without a stdout handle while still reporting a successful spawn.

Common situations: Merging code that builds the child command in two places and one drops the piped stdio configuration; porting to a platform or custom spawner that silently coerces stdio to inherit; wrapping spawn in a helper that returns a reconfigured Child.

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/b33462df87129c40. 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 433685b202)