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
- 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)
- If the command is built elsewhere, make the helper return the configured Command unchanged or re-assert piped stdio at the spawn site
- Add a unit test asserting the spawn setup so a stdio regression fails at test time
- 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
- Always configure Stdio::piped() for stdout and stderr in the same expression as spawn
- Keep child-command construction and spawn in one place; avoid helpers that return reconfigured Children
- Add a test exercising run_lane_log_proxy end-to-end so stdio regressions fail in CI
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
- lane child stderr was not piped
- 125
- connection stdin poisoned by an earlier panic
- event recovery buffer fits u64
- event transaction runs once
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)