{"record":{"id":"f50b50ea71e91455","repo":"Hmbown/CodeWhale","slug":"pr-stdout-reader-failed","errorCode":null,"errorMessage":"PR stdout reader failed","messagePattern":"PR stdout reader failed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/tools/review_pr.rs","lineNumber":499,"sourceCode":"        .take()\n        .context(\"PR command stdout unavailable\")?;\n    let stderr = child\n        .stderr\n        .take()\n        .context(\"PR command stderr unavailable\")?;\n    let stdout = std::thread::spawn(move || read_bounded(stdout, MAX_OUTPUT_BYTES));\n    let stderr = std::thread::spawn(move || read_bounded(stderr, 64 * 1024));\n    let status = match child.wait_timeout(Duration::from_secs(60))? {\n        Some(status) => status,\n        None => {\n            let _ = child.kill();\n            let _ = child.wait();\n            bail!(\"PR input command timed out; no partial output was accepted\");\n        }\n    };\n    let stdout = stdout\n        .join()\n        .map_err(|_| anyhow::anyhow!(\"PR stdout reader failed\"))??;\n    let stderr = stderr\n        .join()\n        .map_err(|_| anyhow::anyhow!(\"PR stderr reader failed\"))??;\n    if stdout.len() > MAX_OUTPUT_BYTES || stderr.len() > 64 * 1024 {\n        bail!(\n            \"PR input exceeds the bounded capture limit (8 MiB diff); no partial output was accepted\"\n        );\n    }\n    if !status.success() {\n        bail!(\n            \"PR input command failed: {}\",\n            String::from_utf8_lossy(&stderr).trim()\n        );\n    }\n    String::from_utf8(stdout).context(\"PR diff is not valid UTF-8; no lossy review is accepted\")\n}\n\n#[cfg(test)]","sourceCodeStart":481,"sourceCodeEnd":517,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/tools/review_pr.rs#L481-L517","documentation":"`run_command` captures child stdout on a dedicated reader thread; after the process exits it `join()`s that thread. If the reader thread panicked, `join()` returns `Err`, and the tool converts that into \"PR stdout reader failed\" so no partial output is accepted. It indicates an internal capture failure, not a command failure.","triggerScenarios":"The stdout reader thread panics (e.g. allocation failure on an unexpectedly huge stream, mutex poisoning, or a bug in the reader loop) while `fetch_view`, `fetch_diff`, `context_blob`, or `git` runs a subprocess.","commonSituations":"Extremely large subprocess output stressing the reader; a poisoned shared buffer after an earlier panic; running in a memory-constrained environment.","solutions":["Retry the command — a transient panic in the reader may not recur","Check available memory; large diffs/outputs can stress the capture buffer","Reduce subprocess output size (narrow the diff or git command) and re-run","If reproducible, report/inspect the reader-thread panic — it is an internal bug"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"match run_command(ws, Program::Gh, &args) { Err(e) if e.to_string().contains(\"stdout reader failed\") => run_with_retry(ws, Program::Gh, &args, 2), Err(e) => Err(e), Ok(v) => Ok(v) }","preventionTips":["Retry bounded subprocess capture on transient reader failures","Keep subprocess output within MAX_OUTPUT_BYTES to reduce reader stress","Watch for reproducible panics — they indicate a bug in the reader threads, not your inputs"],"tags":["subprocess","io","thread","review-pr"],"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"}