{"record":{"id":"227de8d715752939","repo":"openai/codex","slug":"typescript-header-worker-panicked","errorCode":null,"errorMessage":"TypeScript header worker panicked","messagePattern":"TypeScript header worker panicked","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"codex-rs/app-server-protocol/src/export.rs","lineNumber":162,"sourceCode":"        let worker_count = thread::available_parallelism()\n            .map_or(1, usize::from)\n            .min(ts_files.len().max(1));\n        let chunk_size = ts_files.len().div_ceil(worker_count);\n        thread::scope(|scope| -> Result<()> {\n            let mut workers = Vec::new();\n            for chunk in ts_files.chunks(chunk_size.max(1)) {\n                workers.push(scope.spawn(move || -> Result<()> {\n                    for file in chunk {\n                        prepend_header_if_missing(file)?;\n                    }\n                    Ok(())\n                }));\n            }\n\n            for worker in workers {\n                worker\n                    .join()\n                    .map_err(|_| anyhow!(\"TypeScript header worker panicked\"))??;\n            }\n\n            Ok(())\n        })?;\n    }\n\n    // Optionally run Prettier on all generated TS files.\n    if options.run_prettier\n        && let Some(prettier_bin) = prettier\n        && !ts_files.is_empty()\n    {\n        let status = Command::new(prettier_bin)\n            .arg(\"--write\")\n            .arg(\"--log-level\")\n            .arg(\"warn\")\n            .args(ts_files.iter().map(|p| p.as_os_str()))\n            .status()\n            .with_context(|| format!(\"Failed to invoke Prettier at {}\", prettier_bin.display()))?;","sourceCodeStart":144,"sourceCodeEnd":180,"githubUrl":"https://github.com/openai/codex/blob/339751715c64496cb86246bfb3935f40e309dd3d/codex-rs/app-server-protocol/src/export.rs#L144-L180","documentation":"generate_ts_with_options prepends the GENERATED CODE header to every emitted .ts file using scoped worker threads. worker.join() returning Err means a worker panicked mid-chunk, and the panic payload is discarded in favor of this anyhow error, so the original panic message only reaches stderr. The root cause is whatever made the header-prepend path panic, typically an I/O failure on a file in the output tree.","triggerScenarios":"Running the TS export with ensure_headers enabled while a worker panics: filesystem errors unwrapped inside the header-prepend path, files concurrently removed or locked by another process, or an invariant violation introduced by a refactor of the prepend logic.","commonSituations":"Two export processes writing the same out_dir at once; editors, watchers, or antivirus locking generated files; a code change that turned a handled error in the prepend path into an unwrap.","solutions":["Rerun with RUST_BACKTRACE=1: the worker's panic message and location still print to stderr even though join drops the payload; that names the failing line.","Ensure nothing else writes or locks the output directory during export (parallel just targets, file watchers).","Check permissions on the out_dir tree.","If the panic points inside the header-prepend logic, fix the unwrap or expect there; this error is only a wrapper."],"exampleFix":"// before\nworker.join().map_err(|_| anyhow!(\"TypeScript header worker panicked\"))??;\n\n// after - keep the panic message instead of discarding it\nworker.join().map_err(|payload| {\n    let msg = payload\n        .downcast_ref::<&str>()\n        .map(|s| (*s).to_string())\n        .or_else(|| payload.downcast_ref::<String>().cloned())\n        .unwrap_or_else(|| \"unknown panic\".to_string());\n    anyhow!(\"TypeScript header worker panicked: {msg}\")\n})??;","handlingStrategy":"try-catch","validationCode":"// Verify the output tree is writable before export:\nlet probe = out_dir.join(\".write-probe\");\nstd::fs::write(&probe, b\"\")?;\nstd::fs::remove_file(&probe)?;","typeGuard":null,"tryCatchPattern":"if let Err(err) = generate_ts_with_options(&out_dir, prettier.as_deref(), opts) {\n    if err.to_string().contains(\"header worker panicked\") {\n        // Re-run once, serially, with RUST_BACKTRACE=1 to capture the worker\n        // panic; concurrent writers on out_dir are the usual culprit.\n    }\n}","preventionTips":["Never run two exports into the same output directory concurrently.","Keep RUST_BACKTRACE=1 set in CI for export targets so swallowed panics remain visible.","Treat this error as a wrapper: the actionable stack trace is on stderr from the panicked worker, not in the error itself."],"tags":["codegen","typescript","thread-panic","export","concurrency"],"backgroundTag":"thread-panic","analyzedSha":"339751715c64496cb86246bfb3935f40e309dd3d","analyzedAt":"2026-08-25T05:35:09.876Z","schemaVersion":2},"datasetVersion":"2026-08-25T06:17:31.827Z"}