fix(cli): prevent statusline spawn EBADF from crashing CLI (#3264)#3310
fix(cli): prevent statusline spawn EBADF from crashing CLI (#3264)#3310
Conversation
📋 Review SummaryThis PR addresses issue #3264 by fixing a crash that occurs when 🔍 General Feedback
🎯 Specific Feedback🔵 Low
✅ Highlights
|
1f8a720 to
48ea9b2
Compare
Code Coverage Summary
CLI Package - Full Text ReportCore Package - Full Text ReportFor detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run. |
48ea9b2 to
46b2223
Compare
child_process.exec() throws synchronously for spawn errors libuv does not report via the async 'error' event (EBADF, EINVAL, …). On macOS with Node 22, EBADF can surface during stdio pipe setup, and the uncaught throw escapes the debounce setTimeout callback and crashes the CLI. Wrap the exec call in try/catch so a failing statusline degrades to no output while the rest of the CLI keeps running.
46b2223 to
76e40f5
Compare
wenshao
left a comment
There was a problem hiding this comment.
No issues found. LGTM! ✅ — gpt-5.4 via Qwen Code /review
BZ-D
left a comment
There was a problem hiding this comment.
Overall this is a well-scoped and well-documented fix. The try/catch placement is correct, tests cover both the crash and recovery paths, and the PR description is excellent. A few minor observations below (inline comments), but nothing blocking.
Summary
Fixes #3264.
child_process.exec()can throw synchronously for spawn errors that libuv does not report via the async'error'event. On macOS with Node 22,EBADFsurfaces fromChildProcess.spawn(during stdio pipe setup), and the uncaught throw escapes the debouncesetTimeoutcallback →uncaughtException→ CLI crashes after every assistant response whenui.statusLineis configured.Node reports a short allowlist of errors (
EACCES,EAGAIN,EMFILE,ENFILE,ENOENT,ENOSYS,EPERM) asynchronously via'error'; everything else — includingEBADF— is thrown directly fromChildProcess.prototype.spawn.This PR wraps the
exec()call inuseStatusLinewith atry/catchso a failing statusline degrades to no output while the rest of the CLI keeps running. The hook's generation counter already invalidates in-flight callbacks, so subsequent state changes recover normally.Scope and caveats
EBADFhappens on macOS Node 22 during stdio pipe setup — it may be a Node.js / libuv bug, a TTY-raw-mode race, or FD pressure from Ink's rendering. Regardless, a failing statusline command should never take the CLI down, so catching at the caller is the right layer.STATUS_LINEdebug logger, visible only withqwen -d). This matches the existing EPIPE handling a few lines below and is strictly better than crashing, but worth flagging.useStatusLineexec callsite is patched. Otherchild_process.exec/spawncall sites in the codebase may have the same sync-throw exposure — they are not audited by this PR. Happy to do a follow-up sweep if desired.ansiRegex3 is not a functionbug still exists. It's an esbuild ESM/CJS interop issue in the bundledboxen→string-width→ansi-regexchain that fires from the error renderer. This PR prevents the path that was reaching it, so the symptom is gone, but the underlying bundling bug remains latent and will resurface for any other unhandled error that reachesboxen. Worth tracking in a separate issue.Changes
packages/cli/src/ui/hooks/useStatusLine.ts— wrapexec()intry/catch; on failure log to theSTATUS_LINEdebug logger, clear output, and return. Generation counter andactiveChildRefalready handle recovery on the next state change.packages/cli/src/ui/hooks/useStatusLine.test.ts— two new tests: (1) synchronousEBADFthrow does not crash the hook, (2) subsequent state changes recover and re-execute successfully (verifies no wedged refs).Test plan
npx vitest run packages/cli/src/ui/hooks/useStatusLine.test.ts— 32/32 passnpx vitest run packages/cli/src/ui/hooks/— 635/635 pass (no regressions in neighbor hooks)npx tsc --noEmit -p packages/cli/tsconfig.json— cleannpx eslinton modified files — cleanui.statusLineconfigured (requires a macOS environment; reporter's repro steps in ui.statusLine crashes CLI with spawn EBADF on macOS, then hits ansiRegex3 is not a function #3264 can be used)