{"record":{"id":"3a08be5250cbffe6","repo":"Hmbown/CodeWhale","slug":"inline-stdout-logger-panicked","errorCode":null,"errorMessage":"inline stdout logger panicked","messagePattern":"inline stdout logger panicked","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/lane/src/runtime.rs","lineNumber":1018,"sourceCode":"            .stdout\n            .take()\n            .context(\"inline lane child stdout was not piped\")?;\n        let stderr = child\n            .stderr\n            .take()\n            .context(\"inline lane child stderr was not piped\")?;\n        let stdout_log = record.log_path.clone();\n        let stderr_log = record.log_path.clone();\n        let stdout_thread =\n            thread::spawn(move || stream_child_output(stdout, stdout_log, \"stdout\"));\n        let stderr_thread =\n            thread::spawn(move || stream_child_output(stderr, stderr_log, \"stderr\"));\n        let status = child\n            .wait()\n            .with_context(|| format!(\"wait for inline command {:?}\", spec.command))?;\n        let stdout_result = stdout_thread\n            .join()\n            .map_err(|_| anyhow::anyhow!(\"inline stdout logger panicked\"))?;\n        let stderr_result = stderr_thread\n            .join()\n            .map_err(|_| anyhow::anyhow!(\"inline stderr logger panicked\"))?;\n        let logging_error = stdout_result.err().or_else(|| stderr_result.err());\n\n        if status.success() && logging_error.is_none() {\n            append_log_event(\n                &record.log_path,\n                serde_json::json!({\"type\": \"lane_completed\", \"lane_id\": record.id}),\n            )?;\n            let _ = registry.mark_terminal_if_active(record, LaneStatus::Completed)?;\n        } else {\n            append_log_event(\n                &record.log_path,\n                serde_json::json!({\n                    \"type\": \"lane_failed\",\n                    \"lane_id\": record.id,\n                    \"status\": format!(\"{status}\"),","sourceCodeStart":1000,"sourceCodeEnd":1036,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/lane/src/runtime.rs#L1000-L1036","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nstream_to_log(&mut stdout, &log).expect(\"stdout logging failed\");\n// after\nstream_to_log(&mut stdout, &log)\n    .with_context(|| format!(\"log stdout to {}\", log.display()))?;","handlingStrategy":"try-catch","validationCode":"if let Some(dir) = record.log_path.parent() {\n    assert!(dir.exists(), \"inline log dir missing: {}\", dir.display());\n}\nassert!(std::fs::OpenOptions::new().append(true).create(true).open(&record.log_path).is_ok());","typeGuard":"fn join_ok<T>(h: std::thread::JoinHandle<T>) -> Option<T> { h.join().ok() }","tryCatchPattern":"match lane_start_inline(spec) {\n    Err(e) if e.to_string().contains(\"inline stdout logger panicked\") => {\n        eprintln!(\"stdout logger thread panicked: {e:#}; verify log path and disk space, then retry\");\n    }\n    Err(e) => eprintln!(\"start failed: {e:#}\"),\n    Ok(record) => { /* normal path */ }\n}","preventionTips":["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."],"tags":["panic","thread","stdout","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"}