Hmbown/CodeWhale · error
lane proxy stdout logger panicked
Error message
lane proxy stdout logger panicked
What it means
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.
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.
Example fix
// before: panic inside the logging thread on I/O failure
let mut file = File::options().append(true).open(path).unwrap();
// after: propagate the error so join() yields Err(result), not a panic
let file = File::options().append(true).open(path)
.with_context(|| format!("open log {}", path.display()))?;
writeln!(file, "{}", line).with_context(|| "append log line")?; Defensive patterns
Strategy: try-catch
Validate before calling
let log_dir = log_path.parent().unwrap();
assert!(log_dir.exists(), "lane log dir missing: {}", log_dir.display());
let probe = std::fs::OpenOptions::new().append(true).create(true).open(&log_path);
assert!(probe.is_ok(), "lane log not writable: {:?}", probe.err()); Type guard
fn thread_result_ok<T>(r: std::thread::Result<T>) -> Option<T> { r.ok() } Try / catch
match run_lane_log_proxy(spec) {
Ok(code) if code != LANE_PROXY_FAILURE_EXIT_CODE => { /* normal */ }
Ok(code) => eprintln!("lane proxy logging failed; inspect lane log for lane_proxy_error"),
Err(e) if e.to_string().contains("panicked") => {
eprintln!("stdout logger thread panicked: {e:#}");
// check log dir permissions/disk, then retry
}
Err(e) => eprintln!("lane proxy failed: {e:#}"),
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- inline stderr logger panicked
- inline stdout logger panicked
- lane proxy stderr logger panicked
- ApplyPatchPreflight should serialize
- bing result regex pattern is valid
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/79146684db9f7224.
Report an issue: GitHub.
Appendix: source
Thrown at crates/lane/src/runtime.rs:503
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"))?;
let stderr_result = stderr_thread
.join()
.map_err(|_| anyhow::anyhow!("lane proxy stderr logger panicked"))?;
let mut exit_code = exit_status_code(status);
if let Some(error) = stdout_result.err().or_else(|| stderr_result.err()) {
append_proxy_failure(&log_path, &lane_id, &error)?;
exit_code = LANE_PROXY_FAILURE_EXIT_CODE;
}
write_lane_exit_receipt(&receipt_path, &receipt_tmp_path, &lane_id, exit_code)?;
Ok(exit_code)
}
fn read_lane_exit_receipt(log_path: &Path, lane_id: &str) -> Result<Option<LaneExitReceipt>> {
let path = lane_exit_receipt_path(log_path);
let metadata = match std::fs::metadata(&path) {
Ok(metadata) => metadata,
Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(None),
Err(err) => return Err(err).with_context(|| format!("stat {}", path.display())),View on GitHub (pinned to 73e0f67d83)