fix(ssh-tunnel): handle spawn error to prevent unhandled rejection crash#99800
Conversation
When /usr/bin/ssh is missing (ENOENT), spawn() emits an async 'error' event. Without a handler, the unhandled error crashes the process and the Promise.race hangs until timeout. Forward the spawn error into the rejection path so tunnel setup fails gracefully. Co-Authored-By: Claude <noreply@anthropic.com>
|
Codex review: needs maintainer review before merge. Reviewed July 4, 2026, 1:44 AM ET / 05:44 UTC. Summary PR surface: Source 0, Tests +42. Total +42 across 2 files. Reproducibility: yes. Source inspection shows current main lacks a child Review metrics: none identified. Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Next step before merge
Security Review detailsBest possible solution: Land this narrow failed-spawn mitigation after ordinary maintainer review and required checks, while keeping the broader trusted SSH executable-resolution work in #83289 and #93030 separate. Do we have a high-confidence way to reproduce the issue? Yes. Source inspection shows current main lacks a child Is this the best way to solve the issue? Yes for the narrow crash and cleanup-delay mitigation. Adding the error listener to the existing startup race and respecting AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against acdaf8ae31e6. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Source 0, Tests +42. Total +42 across 2 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
|
Co-Authored-By: Claude <noreply@anthropic.com>
|
maintainer repair and exact-head proof are complete on changes:
validation:
the PR body now contains the durable behavior evidence. @clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. |
|
Merged via squash.
|
…ash (openclaw#99800) * fix(ssh-tunnel): handle spawn error to prevent unhandled rejection crash When /usr/bin/ssh is missing (ENOENT), spawn() emits an async 'error' event. Without a handler, the unhandled error crashes the process and the Promise.race hangs until timeout. Forward the spawn error into the rejection path so tunnel setup fails gracefully. Co-Authored-By: Claude <noreply@anthropic.com> * fix(ssh-tunnel): add missing localPortPreferred in test call Co-Authored-By: Claude <noreply@anthropic.com> * fix(ssh-tunnel): avoid failed-spawn cleanup delay --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
…ash (openclaw#99800) * fix(ssh-tunnel): handle spawn error to prevent unhandled rejection crash When /usr/bin/ssh is missing (ENOENT), spawn() emits an async 'error' event. Without a handler, the unhandled error crashes the process and the Promise.race hangs until timeout. Forward the spawn error into the rejection path so tunnel setup fails gracefully. Co-Authored-By: Claude <noreply@anthropic.com> * fix(ssh-tunnel): add missing localPortPreferred in test call Co-Authored-By: Claude <noreply@anthropic.com> * fix(ssh-tunnel): avoid failed-spawn cleanup delay --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Vincent Koc <vincentkoc@ieee.org> (cherry picked from commit c6ed9d8)
What Problem This Solves
Fixes an issue where failing to spawn the SSH executable could escape as an unhandled child-process error and delay gateway status by another 1.5 seconds during cleanup.
Why This Change Was Made
The tunnel startup race now listens for the child
errorevent. Cleanup returns immediately when SIGTERM cannot be delivered, matching Node's failed-spawn lifecycle where no PID exists,kill()returns false, and onlyerrorthencloseare emitted.User Impact
Gateway status reports missing or unspawnable SSH failures promptly instead of crashing or waiting for an
exitevent that will never arrive. Live SSH children retain the existing graceful-then-forced cleanup behavior.Evidence
node scripts/run-vitest.mjs src/infra/ssh-tunnel.test.ts(7 passed).{"killed":false,"exitCode":-2,"killResult":false,"events":[{"event":"error","code":"ENOENT"},{"event":"close","code":-2,"signal":null}]}kill() === false,errorfollowed byclose, noexit, and zero pending cleanup timers.oxfmtandgit diff --checkpassed.1f8149cf92daa1d91a94a504a7acfcfef0302e4f.