[GH-ISSUE #8049] [Bug] CLI dispatcher exits with status 0 on cleanup failure when exit callback receives undefined code #4663

Open
opened 2026-10-31 00:52:10 +00:00 by bsakaguchi · 0 comments
Owner

Originally created by @Sarthak-Pandey on GitHub (Oct 29, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/8049

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

CLI

Observed behavior

Source-only finding in scripts/rea.mjs (lines 158-175).

In the CLI dispatcher's serve runner, the exit callback manages termination and asynchronous worker cleanup:

exit: (code) => {
  exiting = true;
  void (async () => {
    let status = code;
    try {
      await closeCliJavaScriptResources();
    } catch (cleanup) {
      process.stderr.write(
        `Failed to close the JavaScript analysis worker: ${cleanupFailureText(cleanup)}\n`,
      );
      if (status === 0) status = 1;
    }
    try {
      await resultOutput?.finishSdkOutput(code);
    } catch (cause) {
      if (!stdoutClosed)
        process.stderr.write(`${cleanupFailureText(cause)}\n`);
      if (status === 0) status = 1;
    }
    process.exit(stdoutClosed ? SIGPIPE_EXIT_STATUS : status);
  })();
}


### Reproduction

1. Source-only inspection of scripts/rea.mjs lines 158-175.
2. Incur or CLI runner invokes the `exit` callback where `code` is undefined (or omitted).
3. JavaScript analysis worker cleanup (`closeCliJavaScriptResources()`) fails and throws an error.
4. Error message is written to stderr: `Failed to close the JavaScript analysis worker: ...`
5. Observe process exit code in calling shell ($? or $LASTEXITCODE).
6. Observed: Shell receives exit code 0 despite the cleanup failure.

### Expected behavior

When worker cleanup fails, the process exit code must become non-zero (`1`) even if `code` was originally `undefined`.

Consistent with scripts/rea.mjs line 196 where unassigned process exit code is guarded:
`typeof process.exitCode === "number" ? process.exitCode : 0`

The exit callback on line 158 should normalize `status`:
`let status = typeof code === "number" ? code : 0;`
so that if cleanup fails, `status = 1` is correctly assigned and returned to the OS.


### Impact and scope

- CI/CD & Pipeline Reliability: Automated pipelines, bash/powershell scripts, and test runners rely on process exit codes. A cleanup failure returning exit code 0 prevents automation from detecting leaked worker processes.
- Process Lifecycle: An unclosed analysis worker remains owned or in an inconsistent state without raising a non-zero exit alert.


### REA version

6.8.0 (commit b352b4668039b70d53912baccf2ae273cd24610b)

### Runtime and platform

Node.js v22.14.0; Windows 11 x64 (Cross-platform boundary)

### Target and artifact context

CLI dispatcher runtime boundary (scripts/rea.mjs)

### Interface

CLI

### Earliest failure stage

Cleanup, cancellation, or lifecycle

### Relevant output or evidence

Command or MCP request actually run:
Source inspection of scripts/rea.mjs

Relevant result, error, or artifact:
scripts/rea.mjs:158: `let status = code;`
scripts/rea.mjs:165: `if (status === 0) status = 1;`
scripts/rea.mjs:174: `process.exit(stdoutClosed ? SIGPIPE_EXIT_STATUS : status);`

Contrast with existing defensive normalization at line 196:
`typeof process.exitCode === "number" ? process.exitCode : 0`


### Typed outcome or recovery expected

Exit code should be 1 whenever worker cleanup or SDK output finalization fails, regardless of whether `code` was a number or undefined.


### Analysis or suspected cause

Root cause is missing type normalization on the `code` argument before performing integer equality checks (`status === 0`). In JavaScript, `undefined === 0` is false, bypassing both recovery reassignments.


### Additional context

Proposed fix diff:

```diff
--- a/scripts/rea.mjs
+++ b/scripts/rea.mjs
@@ -155,7 +155,7 @@
           exit: (code) => {
             exiting = true;
             void (async () => {
-              let status = code;
+              let status = typeof code === "number" ? code : 0;
               try {
                 await closeCliJavaScriptResources();
               } catch (cleanup) {
Originally created by @Sarthak-Pandey on GitHub (Oct 29, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/8049 ### Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area CLI ### Observed behavior Source-only finding in scripts/rea.mjs (lines 158-175). In the CLI dispatcher's `serve` runner, the `exit` callback manages termination and asynchronous worker cleanup: ```javascript exit: (code) => { exiting = true; void (async () => { let status = code; try { await closeCliJavaScriptResources(); } catch (cleanup) { process.stderr.write( `Failed to close the JavaScript analysis worker: ${cleanupFailureText(cleanup)}\n`, ); if (status === 0) status = 1; } try { await resultOutput?.finishSdkOutput(code); } catch (cause) { if (!stdoutClosed) process.stderr.write(`${cleanupFailureText(cause)}\n`); if (status === 0) status = 1; } process.exit(stdoutClosed ? SIGPIPE_EXIT_STATUS : status); })(); } ### Reproduction 1. Source-only inspection of scripts/rea.mjs lines 158-175. 2. Incur or CLI runner invokes the `exit` callback where `code` is undefined (or omitted). 3. JavaScript analysis worker cleanup (`closeCliJavaScriptResources()`) fails and throws an error. 4. Error message is written to stderr: `Failed to close the JavaScript analysis worker: ...` 5. Observe process exit code in calling shell ($? or $LASTEXITCODE). 6. Observed: Shell receives exit code 0 despite the cleanup failure. ### Expected behavior When worker cleanup fails, the process exit code must become non-zero (`1`) even if `code` was originally `undefined`. Consistent with scripts/rea.mjs line 196 where unassigned process exit code is guarded: `typeof process.exitCode === "number" ? process.exitCode : 0` The exit callback on line 158 should normalize `status`: `let status = typeof code === "number" ? code : 0;` so that if cleanup fails, `status = 1` is correctly assigned and returned to the OS. ### Impact and scope - CI/CD & Pipeline Reliability: Automated pipelines, bash/powershell scripts, and test runners rely on process exit codes. A cleanup failure returning exit code 0 prevents automation from detecting leaked worker processes. - Process Lifecycle: An unclosed analysis worker remains owned or in an inconsistent state without raising a non-zero exit alert. ### REA version 6.8.0 (commit b352b4668039b70d53912baccf2ae273cd24610b) ### Runtime and platform Node.js v22.14.0; Windows 11 x64 (Cross-platform boundary) ### Target and artifact context CLI dispatcher runtime boundary (scripts/rea.mjs) ### Interface CLI ### Earliest failure stage Cleanup, cancellation, or lifecycle ### Relevant output or evidence Command or MCP request actually run: Source inspection of scripts/rea.mjs Relevant result, error, or artifact: scripts/rea.mjs:158: `let status = code;` scripts/rea.mjs:165: `if (status === 0) status = 1;` scripts/rea.mjs:174: `process.exit(stdoutClosed ? SIGPIPE_EXIT_STATUS : status);` Contrast with existing defensive normalization at line 196: `typeof process.exitCode === "number" ? process.exitCode : 0` ### Typed outcome or recovery expected Exit code should be 1 whenever worker cleanup or SDK output finalization fails, regardless of whether `code` was a number or undefined. ### Analysis or suspected cause Root cause is missing type normalization on the `code` argument before performing integer equality checks (`status === 0`). In JavaScript, `undefined === 0` is false, bypassing both recovery reassignments. ### Additional context Proposed fix diff: ```diff --- a/scripts/rea.mjs +++ b/scripts/rea.mjs @@ -155,7 +155,7 @@ exit: (code) => { exiting = true; void (async () => { - let status = code; + let status = typeof code === "number" ? code : 0; try { await closeCliJavaScriptResources(); } catch (cleanup) {
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#4663
No description provided.