Hmbown/CodeWhale · error
inline stdout logger panicked
Error message
inline stdout logger panicked
What it means
In the inline (non-tmux-proxied) `start` path, the child's stdout is pumped to a log by a dedicated thread; after `child.wait()` the thread is joined and a panic in it produces this anyhow error. It means the inline stdout logger died before finishing, so the lane log is incomplete and `start` aborts instead of recording a normal lane completion.
Solutions
- Read the panic message/backtrace emitted when the thread died to identify the actual I/O or logic failure in `stream_child_output`.
- Verify the lane log path exists, is writable, and its filesystem has space for the whole command duration.
- Check for races where the log file or its directory is cleaned up while the command is still running.
- Change `stream_child_output` to propagate errors (return Result) rather than panicking, so the caller gets the underlying I/O error instead of a bare panic marker.
- Retry the start once the environment issue is fixed; also confirm `status.success()` handling is skipped as intended when logging_error is set.
Example fix
// before
stream_to_log(&mut stdout, &log).expect("stdout logging failed");
// after
stream_to_log(&mut stdout, &log)
.with_context(|| format!("log stdout to {}", log.display()))?; Defensive patterns
Strategy: try-catch
Validate before calling
if let Some(dir) = record.log_path.parent() {
assert!(dir.exists(), "inline log dir missing: {}", dir.display());
}
assert!(std::fs::OpenOptions::new().append(true).create(true).open(&record.log_path).is_ok()); Type guard
fn join_ok<T>(h: std::thread::JoinHandle<T>) -> Option<T> { h.join().ok() } Try / catch
match lane_start_inline(spec) {
Err(e) if e.to_string().contains("inline stdout logger panicked") => {
eprintln!("stdout logger thread panicked: {e:#}; verify log path and disk space, then retry");
}
Err(e) => eprintln!("start failed: {e:#}"),
Ok(record) => { /* normal path */ }
} Prevention
- Validate the log path (exists, writable, enough space) before spawning the inline command.
- Don't tear down the lane log while the command or its logger threads are alive.
- Make `stream_child_output` fallible; panics lose the real I/O error at join().
- Exercise the tmux dry-run roundtrip with noisy stdout to shake out logger panics.
When it happens
Trigger: Calling the inline lane `start` with a command whose stdout logger thread (`stream_child_output(stdout, stdout_log, "stdout")`) panics — e.g. unwrap on a failed write/append to `record.log_path` while the command is producing output.
Common situations: Log file's directory removed or permissions changed while the inline command runs; disk full during verbose output; a panic path in `stream_child_output` triggered by unusual output bytes; tmux dry-run roundtrip tests hitting a race where the log handle is closed early.
Related errors
- inline stderr logger panicked
- lane proxy stderr 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/3a08be5250cbffe6.
Report an issue: GitHub.
Appendix: source
Thrown at crates/lane/src/runtime.rs:1018
.stdout
.take()
.context("inline lane child stdout was not piped")?;
let stderr = child
.stderr
.take()
.context("inline lane child stderr was not piped")?;
let stdout_log = record.log_path.clone();
let stderr_log = record.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 = child
.wait()
.with_context(|| format!("wait for inline command {:?}", spec.command))?;
let stdout_result = stdout_thread
.join()
.map_err(|_| anyhow::anyhow!("inline stdout logger panicked"))?;
let stderr_result = stderr_thread
.join()
.map_err(|_| anyhow::anyhow!("inline stderr logger panicked"))?;
let logging_error = stdout_result.err().or_else(|| stderr_result.err());
if status.success() && logging_error.is_none() {
append_log_event(
&record.log_path,
serde_json::json!({"type": "lane_completed", "lane_id": record.id}),
)?;
let _ = registry.mark_terminal_if_active(record, LaneStatus::Completed)?;
} else {
append_log_event(
&record.log_path,
serde_json::json!({
"type": "lane_failed",
"lane_id": record.id,
"status": format!("{status}"),View on GitHub (pinned to 73e0f67d83)