{"record":{"id":"79146684db9f7224","repo":"Hmbown/CodeWhale","slug":"lane-proxy-stdout-logger-panicked","errorCode":null,"errorMessage":"lane proxy stdout logger panicked","messagePattern":"lane proxy stdout logger panicked","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/lane/src/runtime.rs","lineNumber":503,"sourceCode":"        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        }\n    };\n    let stdout_result = stdout_thread\n        .join()\n        .map_err(|_| anyhow::anyhow!(\"lane proxy stdout logger panicked\"))?;\n    let stderr_result = stderr_thread\n        .join()\n        .map_err(|_| anyhow::anyhow!(\"lane proxy stderr logger panicked\"))?;\n    let mut exit_code = exit_status_code(status);\n    if let Some(error) = stdout_result.err().or_else(|| stderr_result.err()) {\n        append_proxy_failure(&log_path, &lane_id, &error)?;\n        exit_code = LANE_PROXY_FAILURE_EXIT_CODE;\n    }\n    write_lane_exit_receipt(&receipt_path, &receipt_tmp_path, &lane_id, exit_code)?;\n    Ok(exit_code)\n}\n\nfn read_lane_exit_receipt(log_path: &Path, lane_id: &str) -> Result<Option<LaneExitReceipt>> {\n    let path = lane_exit_receipt_path(log_path);\n    let metadata = match std::fs::metadata(&path) {\n        Ok(metadata) => metadata,\n        Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(None),\n        Err(err) => return Err(err).with_context(|| format!(\"stat {}\", path.display())),","sourceCodeStart":485,"sourceCodeEnd":521,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/lane/src/runtime.rs#L485-L521","documentation":"run_lane_log_proxy spawns two threads (`stream_child_output`) to pipe the child's stdout and stderr into the lane NDJSON log, then joins them after the child exits. If the stdout logging thread panicked while holding its JoinHandle result, `join()` returns Err and this anyhow error is raised instead of the real cause. It signals a bug or I/O failure inside the logging worker, not a failure of the proxied child command itself.","triggerScenarios":"Calling `run_lane_log_proxy` (or the `lane log-proxy` CLI path) where the stdout streaming thread panics mid-run — e.g. `stream_child_output` unwraps a failed append/write to `log_path`, or the log file handle becomes invalid (deleted directory, unwritable path, disk full) on a code path that panics instead of returning an error.","commonSituations":"Log directory removed while the lane child is running; permissions on the lane log changed mid-flight; a panic inside `stream_child_output`'s formatting/serialization on malformed child output; running many lanes concurrently and exhausting file descriptors so writes panic on unwrap.","solutions":["Check the panic message in stderr/stdout of the proxy process (the panic prints where the thread died) and fix the underlying cause in `stream_child_output` — usually log-file I/O.","Verify `log_path`'s parent directory exists and is writable for the whole lifetime of the child process.","Check available disk space and inode/fd limits if the lane runs long or produces high output volume.","Report/patch `stream_child_output` if it panics on recoverable I/O errors instead of returning them; the join Err payload is always the string panic message, so the real error is lost.","Re-run the lane after fixing; the exit receipt will show LANE_PROXY_FAILURE_EXIT_CODE for this run."],"exampleFix":"// before: panic inside the logging thread on I/O failure\nlet mut file = File::options().append(true).open(path).unwrap();\n// after: propagate the error so join() yields Err(result), not a panic\nlet file = File::options().append(true).open(path)\n    .with_context(|| format!(\"open log {}\", path.display()))?;\nwriteln!(file, \"{}\", line).with_context(|| \"append log line\")?;","handlingStrategy":"try-catch","validationCode":"let log_dir = log_path.parent().unwrap();\nassert!(log_dir.exists(), \"lane log dir missing: {}\", log_dir.display());\nlet probe = std::fs::OpenOptions::new().append(true).create(true).open(&log_path);\nassert!(probe.is_ok(), \"lane log not writable: {:?}\", probe.err());","typeGuard":"fn thread_result_ok<T>(r: std::thread::Result<T>) -> Option<T> { r.ok() }","tryCatchPattern":"match run_lane_log_proxy(spec) {\n    Ok(code) if code != LANE_PROXY_FAILURE_EXIT_CODE => { /* normal */ }\n    Ok(code) => eprintln!(\"lane proxy logging failed; inspect lane log for lane_proxy_error\"),\n    Err(e) if e.to_string().contains(\"panicked\") => {\n        eprintln!(\"stdout logger thread panicked: {e:#}\");\n        // check log dir permissions/disk, then retry\n    }\n    Err(e) => eprintln!(\"lane proxy failed: {e:#}\"),\n}","preventionTips":["Pre-flight check that the log path and its parent directory are writable before starting a lane.","Monitor disk space on the volume holding lane logs.","Never unwrap I/O results inside `stream_child_output`; return Result.","Test lanes with log directories removed mid-run to catch panic paths."],"tags":["panic","thread","logging","lane","rust"],"backgroundTag":"thread-interrupted","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}