Hmbown/CodeWhale · error
lane proxy stderr logger panicked
Error message
lane proxy stderr logger panicked
What it means
Identical sibling of the stdout variant: after the proxied child exits, `run_lane_log_proxy` joins the stderr streaming thread; if that thread panicked, `join()` returns Err and this error is raised. It means the thread responsible for capturing the child's stderr into the lane log died unexpectedly, so stderr output may be missing from the log and the lane is marked with the proxy failure exit code.
Solutions
- Inspect the panic backtrace printed by the dying thread to find the real failure inside `stream_child_output`.
- Ensure `log_path` remains present and writable for the full child lifetime; restore permissions or recreate the parent directory.
- Free disk space / raise fd limits if the run exhausted them.
- Patch `stream_child_output` to return io::Errors instead of unwrapping, so join() yields the real error and the log gets a `lane_proxy_error` event with the actual cause.
- Re-run the lane; note the exit receipt records LANE_PROXY_FAILURE_EXIT_CODE even though the child itself may have succeeded.
Example fix
// before
let written = file.write_all(line.as_bytes()).unwrap();
// after
file.write_all(line.as_bytes())
.with_context(|| "lane proxy stderr log append")?; Defensive patterns
Strategy: try-catch
Validate before calling
let meta = std::fs::metadata(log_path.parent().unwrap())?; let writable = !meta.permissions().readonly(); assert!(writable, "lane log parent is read-only");
Type guard
fn logger_thread_survived<T>(r: std::thread::Result<T>) -> bool { r.is_ok() } Try / catch
match run_lane_log_proxy(spec) {
Err(e) if e.to_string().contains("stderr logger panicked") => {
eprintln!("stderr logger thread died: {e:#}; check log file state and re-run");
}
Err(e) => eprintln!("lane proxy failed: {e:#}"),
Ok(code) => { /* use exit receipt */ }
} Prevention
- Keep one append-only log handle per stream opened once, error-checked at open time.
- Ensure log cleanup jobs never delete lane logs while a lane is running.
- Return io::Error from the logging loop instead of panicking on write failures.
- Watch for LANE_PROXY_FAILURE_EXIT_CODE in exit receipts as an early signal of logger trouble.
When it happens
Trigger: Calling `run_lane_log_proxy` where the stderr thread (`stream_child_output(stderr, stderr_log, "stderr")`) panics — typically an unwrap on a failed append to the shared `log_path` or a panic while serializing an NDJSON event.
Common situations: Same as stdout variant: log path deleted/unwritable mid-run, disk full, fd exhaustion, a bug in `stream_child_output` that panics on unexpected input. Especially visible when the child writes large or malformed output to stderr.
Related errors
- inline stderr logger panicked
- inline stdout logger panicked
- lane proxy stdout 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/9ee3ad9d777b83cf.
Report an issue: GitHub.
Appendix: source
Thrown at crates/lane/src/runtime.rs:506
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())),
};
if metadata.len() > MAX_EXIT_RECEIPT_BYTES {
bail!(View on GitHub (pinned to 73e0f67d83)