{"record":{"id":"b33462df87129c40","repo":"Hmbown/CodeWhale","slug":"lane-child-stdout-was-not-piped","errorCode":null,"errorMessage":"lane child stdout was not piped","messagePattern":"lane child stdout was not piped","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/lane/src/runtime.rs","lineNumber":481,"sourceCode":"        Vec::new()\n    };\n\n    let mut child_command = Command::new(&command[0]);\n    child_command.args(&command[1..]);\n    child_command.envs(environment);\n    child_command.stdout(Stdio::piped()).stderr(Stdio::piped());\n    let mut child = match child_command.spawn() {\n        Ok(child) => child,\n        Err(error) => {\n            return fail(\n                anyhow::Error::from(error).context(format!(\"spawn lane child command {command:?}\")),\n                127,\n            );\n        }\n    };\n    let stdout = match child.stdout.take() {\n        Some(stdout) => stdout,\n        None => return fail(anyhow::anyhow!(\"lane child stdout was not piped\"), 125),\n    };\n    let stderr = match child.stderr.take() {\n        Some(stderr) => stderr,\n        None => return fail(anyhow::anyhow!(\"lane child stderr was not piped\"), 125),\n    };\n    let stdout_log = log_path.clone();\n    let stderr_log = log_path.clone();\n    let stdout_thread = thread::spawn(move || stream_child_output(stdout, stdout_log, \"stdout\"));\n    let stderr_thread = thread::spawn(move || stream_child_output(stderr, stderr_log, \"stderr\"));\n\n    let status = match child.wait() {\n        Ok(status) => status,\n        Err(error) => {\n            return fail(\n                anyhow::Error::from(error).context(format!(\"wait for lane child {command:?}\")),\n                LANE_PROXY_FAILURE_EXIT_CODE,\n            );\n        }","sourceCodeStart":463,"sourceCodeEnd":499,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/crates/lane/src/runtime.rs#L463-L499","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nlet mut child_command = Command::new(&command[0]);\nchild_command.args(&command[1..]);\nlet mut child = child_command.spawn()?;\n// after\nlet mut child_command = Command::new(&command[0]);\nchild_command.args(&command[1..]);\nchild_command.stdout(Stdio::piped()).stderr(Stdio::piped());\nlet mut child = child_command.spawn()?;","handlingStrategy":"validation","validationCode":"let mut cmd = Command::new(&command[0]);\ncmd.args(&command[1..]).stdout(Stdio::piped()).stderr(Stdio::piped());\nassert_piped(&cmd); // keep piped stdio config adjacent to spawn; never build the Command elsewhere","typeGuard":"fn piped_stdout(child: &Child) -> Option<&ChildStdout> { child.stdout.as_ref() }","tryCatchPattern":"let stdout = match child.stdout.take() {\n    Some(s) => s,\n    None => return fail(anyhow!(\"lane child stdout was not piped\"), 125),\n};","preventionTips":["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"],"tags":["rust","process-spawn","stdio","invariant"],"backgroundTag":"internal-invariant-violation","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}