[PR #1221] [MERGED] fix(process): stop timed-out commands with the caller's stop signal #2730

Closed
opened 2026-10-17 21:09:36 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/1221
Author: @kaoru0822-kitauji
Created: 10/9/2026
Status: ✅ Merged
Merged: 10/9/2026
Merged by: @kaoru0822-kitauji

Base: main ← Head: fix/exec-stop-signal-timeout


📝 Commits (1)

  • 063de50 fix(process): stop timed-out commands with the caller's stop signal

📊 Changes

2 files changed (+105 additions, -20 deletions)

View changed files

📝 src/process/ExecFileOutput.test.ts (+47 -0)
📝 src/process/ExecFileOutput.ts (+58 -20)

📄 Description

Follow-up to #1194, addressing both unresolved Codex review findings on it.

Problem

execFileOutput({ stopSignal }) exists so the swiftc helper builds can be stopped with SIGINT, which swift-driver needs in order to clean up its frontend jobs before REA removes their files. The timeout path did not honor it:

  1. stopSignal was dropped without an AbortSignal. It was forwarded only when the caller also passed signal. createNativeUiHelperRuntime passes signal only when it has one, so its 60-second compiler timeout still sent Node's default SIGTERM.
  2. No grace-period kill on timeout. The SIGKILL grace timer was installed only by the abort listener. A command that reached timeout received the stop signal from execFile, and if it ignored the signal, execFileOutput waited forever.
  3. A clean exit after the timeout resolved as success. Node's execFile resolves when a killed child exits with code 0. A process that handles the stop signal and exits 0 (the case stopSignal is designed for) therefore returned truncated output as a success.

On main, the new tests show (2) as a hang and (3) as a resolved promise.

Change

When stopSignal is set, execFileOutput handles the timeout itself instead of passing timeout to execFile, just as it already did for abort:

  • At the deadline it sends stopSignal. If the process is still running after the 5-second grace period, it sends SIGKILL.
  • The promise settles only after the process exits, and rejects with code: "ETIMEDOUT", killed: true, the exit signal, and the captured output. The exit error is kept as cause.
  • Whichever stop request comes first (abort or timeout) names the failure. A later request does not resend the signal.

Calls without stopSignal behave as before: Node's timeout and signal handling.

Verification

  • npx vitest run src/process/ExecFileOutput.test.ts: 7 passed. The two new tests fail on main: one resolves as success, the other times out.
  • npm run build:cached && npx vitest run tests/boundary/process/darwinProcessRunTokenReader.test.ts src/process/capture/ProcessHarness.test.ts: 31 passed. This includes the real swiftc helper build on macOS.
  • npm run check:changed: typecheck and lint passed. The one failure is src/artifacts/MachOSliceArtifactReader.test.ts. It also fails on main on this Mac, because it pins the slice sizes of the host's /bin/ls, which differ on this macOS release. It is unrelated to this change.

No Hopper or Ghidra verification was needed; this change does not involve either provider.

🤖 Generated with Claude Code


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/morluto/rea/pull/1221 **Author:** [@kaoru0822-kitauji](https://github.com/kaoru0822-kitauji) **Created:** 10/9/2026 **Status:** ✅ Merged **Merged:** 10/9/2026 **Merged by:** [@kaoru0822-kitauji](https://github.com/kaoru0822-kitauji) **Base:** `main` ← **Head:** `fix/exec-stop-signal-timeout` --- ### 📝 Commits (1) - [`063de50`](https://github.com/morluto/rea/commit/063de5014f19e85c38b6bec5984de4f38e71adaa) fix(process): stop timed-out commands with the caller's stop signal ### 📊 Changes **2 files changed** (+105 additions, -20 deletions) <details> <summary>View changed files</summary> 📝 `src/process/ExecFileOutput.test.ts` (+47 -0) 📝 `src/process/ExecFileOutput.ts` (+58 -20) </details> ### 📄 Description Follow-up to #1194, addressing both unresolved Codex review findings on it. ## Problem `execFileOutput({ stopSignal })` exists so the swiftc helper builds can be stopped with SIGINT, which swift-driver needs in order to clean up its frontend jobs before REA removes their files. The timeout path did not honor it: 1. **`stopSignal` was dropped without an AbortSignal.** It was forwarded only when the caller also passed `signal`. `createNativeUiHelperRuntime` passes `signal` only when it has one, so its 60-second compiler timeout still sent Node's default SIGTERM. 2. **No grace-period kill on timeout.** The SIGKILL grace timer was installed only by the abort listener. A command that reached `timeout` received the stop signal from `execFile`, and if it ignored the signal, `execFileOutput` waited forever. 3. **A clean exit after the timeout resolved as success.** Node's `execFile` resolves when a killed child exits with code 0. A process that handles the stop signal and exits 0 (the case `stopSignal` is designed for) therefore returned truncated output as a success. On main, the new tests show (2) as a hang and (3) as a resolved promise. ## Change When `stopSignal` is set, `execFileOutput` handles the timeout itself instead of passing `timeout` to `execFile`, just as it already did for abort: - At the deadline it sends `stopSignal`. If the process is still running after the 5-second grace period, it sends SIGKILL. - The promise settles only after the process exits, and rejects with `code: "ETIMEDOUT"`, `killed: true`, the exit `signal`, and the captured output. The exit error is kept as `cause`. - Whichever stop request comes first (abort or timeout) names the failure. A later request does not resend the signal. Calls without `stopSignal` behave as before: Node's `timeout` and `signal` handling. ## Verification - `npx vitest run src/process/ExecFileOutput.test.ts`: 7 passed. The two new tests fail on main: one resolves as success, the other times out. - `npm run build:cached && npx vitest run tests/boundary/process/darwinProcessRunTokenReader.test.ts src/process/capture/ProcessHarness.test.ts`: 31 passed. This includes the real swiftc helper build on macOS. - `npm run check:changed`: typecheck and lint passed. The one failure is `src/artifacts/MachOSliceArtifactReader.test.ts`. It also fails on main on this Mac, because it pins the slice sizes of the host's `/bin/ls`, which differ on this macOS release. It is unrelated to this change. No Hopper or Ghidra verification was needed; this change does not involve either provider. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-17 21:09:36 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#2730
No description provided.