[GH-ISSUE #1126] fix(cli): web runtime commands overwrite signal cancellation exit codes #399

Closed
opened 2026-10-15 18:24:45 +00:00 by bsakaguchi · 1 comment
Owner

Originally created by @coygeek on GitHub (Oct 8, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/1126

Summary

observe-web-execution and inspect-web-event-listeners both return a typed cancelled result after SIGINT or SIGTERM, but exit 1 after the shared cancellation boundary sets the conventional signal status. Callers using the process status cannot distinguish these interrupts from ordinary operation failure as they can for commands that retain the cancellation boundary's 130/143 result.

Runtime or environment

Executed published rea-agents 6.0.0, whose release tag points to 6fee42689ae6e95a0182e0d4d0f55644e73e72be, on macOS 27.0 arm64 with Node.js 22.23.2 and npm 12.0.2. Also inspected current main at 84a17d55199b1b46493aea6bf4e41029a3b200fc; its command registrations, cancellation helper and CLI failure logger are unchanged from the release. The main build was not executed.

Area: CLI. Interface: CLI. Earliest failure stage: cleanup, cancellation, or lifecycle. Target context: a disposable loopback HTTP endpoint that accepts the CDP discovery request and deliberately leaves it unanswered. No real browser or page is needed to exercise cancellation during connection setup.

Steps to reproduce

In an empty disposable directory, install the published package locally with npm install --no-save rea-agents@6.0.0, then run the following Python script from that directory. It waits until the command contacts the loopback endpoint before sending the signal, records the exit and typed result, and closes every fixture socket and child process.

import json
import pathlib
import signal
import socket
import subprocess
import threading

rea = str(pathlib.Path("node_modules/.bin/rea").resolve())
for command, extra in [
    ("observe-web-execution", []),
    ("inspect-web-event-listeners", ["body"]),
]:
    for sig in [signal.SIGINT, signal.SIGTERM]:
        server = socket.socket()
        server.bind(("127.0.0.1", 0))
        server.listen()
        server.settimeout(10)
        contacted = threading.Event()
        done = threading.Event()

        def hold():
            try:
                conn, _ = server.accept()
            except OSError:
                return
            try:
                conn.settimeout(10)
                conn.recv(4096)
                contacted.set()
                done.wait(15)
            finally:
                conn.close()

        thread = threading.Thread(target=hold)
        thread.start()
        endpoint = "http://127.0.0.1:" + str(server.getsockname()[1])
        child = subprocess.Popen(
            [rea, command, endpoint, "page-fixture", *extra, "--json"],
            stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True,
        )
        if contacted.wait(8):
            child.send_signal(sig)
        else:
            child.kill()
        try:
            stdout, stderr = child.communicate(timeout=10)
        except subprocess.TimeoutExpired:
            child.kill()
            stdout, stderr = child.communicate()
        done.set()
        server.close()
        thread.join()
        print(command, sig.name, child.returncode, stdout, stderr)

Expected behavior

The shared withCommandCancellation boundary specifies conventional exit codes, 130 for SIGINT and 143 for SIGTERM, after awaiting the operation. These two commands should preserve that status while still emitting their typed cancelled result and completing cleanup.

The CLI guide's broader status table permits cancellation to exit 1 and qualifies signal-derived status with "where the shell or runtime preserves" it. This report concerns the narrower inconsistency where these commands explicitly select 130/143 through the shared helper and then overwrite it, rather than claiming that the guide universally requires signal-derived status.

Actual behavior

All four executed command/signal combinations contacted the fixture and exited 1. Each stdout was one JSON document with code: "cancelled", category: "cancelled", details.cleanup: "complete" and the corresponding operation name. Stderr was empty. This verifies connection-setup cancellation, not instrumentation cleanup after a real page has armed.

observe-web-execution       SIGINT  exit=1 code=cancelled cleanup=complete
observe-web-execution       SIGTERM exit=1 code=cancelled cleanup=complete
inspect-web-event-listeners SIGINT  exit=1 code=cancelled cleanup=complete
inspect-web-event-listeners SIGTERM exit=1 code=cancelled cleanup=complete

As a published-package control, inspect-web-network-capture on a 90-byte HAR containing {"log":{"version":"1.2","creator":{"name":"fixture","version":"1"},"entries":[]}} was interrupted after its owned decoder subprocess appeared. It returned typed cancelled JSON with cleanup: "complete" and exited 130 for SIGINT and 143 for SIGTERM. This control used a tiny capture, not a large-memory workload.

Evidence

  • Expected source: the current shared cancellation helper, including its conventional-exit-code contract and signal mappings.
  • Failure source: the four package subprocess observations above; current web runtime registrations place cancellation inside logging, and the logger subsequently assigns exit 1 for the returned typed error.
  • Evidence provenance: observed
  • Local verification: reproduced
  • Reproduction completeness: complete

The CLI status table provides the broader qualification described above. The existing real-browser acceptance test checks nonzero status, so exit 1 satisfies that test without proving preservation of the helper's exact signal status.

Open and closed issues and PRs were searched for both command names, SIGINT, cancellation, signal exit and exit codes. Closed issue #113 addresses nonzero operation failure status; merged PR #1098 concerns JavaScript cancellation. Neither reports this web-command status overwrite.

Restoration check

Repeat the same four command/signal cases. They should return the same typed cancelled document, close their processes and fixture connections, and exit 130 for SIGINT or 143 for SIGTERM. A normal nonsignal operation error should still exit 1. Preserve the existing real-browser cleanup assertions separately; this connection-setup fixture does not establish that guarantee.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: These two commands lose the signal-specific status selected by their cancellation boundary, so automation relying on that status classifies user interruption as ordinary failure. The result remains nonzero and contains an accurate cancellation document; no evidence loss or cleanup failure was observed.
  • Workaround status: available
  • Workaround basis: The observed JSON result consistently identifies code: "cancelled", allowing callers to distinguish cancellation by parsing stdout instead of relying only on the exit code.
Originally created by @coygeek on GitHub (Oct 8, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/1126 ## Summary `observe-web-execution` and `inspect-web-event-listeners` both return a typed `cancelled` result after SIGINT or SIGTERM, but exit 1 after the shared cancellation boundary sets the conventional signal status. Callers using the process status cannot distinguish these interrupts from ordinary operation failure as they can for commands that retain the cancellation boundary's 130/143 result. ## Runtime or environment Executed published `rea-agents` 6.0.0, whose release tag points to `6fee42689ae6e95a0182e0d4d0f55644e73e72be`, on macOS 27.0 arm64 with Node.js 22.23.2 and npm 12.0.2. Also inspected current `main` at `84a17d55199b1b46493aea6bf4e41029a3b200fc`; its command registrations, cancellation helper and CLI failure logger are unchanged from the release. The main build was not executed. Area: CLI. Interface: CLI. Earliest failure stage: cleanup, cancellation, or lifecycle. Target context: a disposable loopback HTTP endpoint that accepts the CDP discovery request and deliberately leaves it unanswered. No real browser or page is needed to exercise cancellation during connection setup. ## Steps to reproduce In an empty disposable directory, install the published package locally with `npm install --no-save rea-agents@6.0.0`, then run the following Python script from that directory. It waits until the command contacts the loopback endpoint before sending the signal, records the exit and typed result, and closes every fixture socket and child process. ```python import json import pathlib import signal import socket import subprocess import threading rea = str(pathlib.Path("node_modules/.bin/rea").resolve()) for command, extra in [ ("observe-web-execution", []), ("inspect-web-event-listeners", ["body"]), ]: for sig in [signal.SIGINT, signal.SIGTERM]: server = socket.socket() server.bind(("127.0.0.1", 0)) server.listen() server.settimeout(10) contacted = threading.Event() done = threading.Event() def hold(): try: conn, _ = server.accept() except OSError: return try: conn.settimeout(10) conn.recv(4096) contacted.set() done.wait(15) finally: conn.close() thread = threading.Thread(target=hold) thread.start() endpoint = "http://127.0.0.1:" + str(server.getsockname()[1]) child = subprocess.Popen( [rea, command, endpoint, "page-fixture", *extra, "--json"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, ) if contacted.wait(8): child.send_signal(sig) else: child.kill() try: stdout, stderr = child.communicate(timeout=10) except subprocess.TimeoutExpired: child.kill() stdout, stderr = child.communicate() done.set() server.close() thread.join() print(command, sig.name, child.returncode, stdout, stderr) ``` ## Expected behavior The shared `withCommandCancellation` boundary specifies conventional exit codes, 130 for SIGINT and 143 for SIGTERM, after awaiting the operation. These two commands should preserve that status while still emitting their typed cancelled result and completing cleanup. The CLI guide's broader status table permits cancellation to exit 1 and qualifies signal-derived status with "where the shell or runtime preserves" it. This report concerns the narrower inconsistency where these commands explicitly select 130/143 through the shared helper and then overwrite it, rather than claiming that the guide universally requires signal-derived status. ## Actual behavior All four executed command/signal combinations contacted the fixture and exited 1. Each stdout was one JSON document with `code: "cancelled"`, `category: "cancelled"`, `details.cleanup: "complete"` and the corresponding operation name. Stderr was empty. This verifies connection-setup cancellation, not instrumentation cleanup after a real page has armed. ```text observe-web-execution SIGINT exit=1 code=cancelled cleanup=complete observe-web-execution SIGTERM exit=1 code=cancelled cleanup=complete inspect-web-event-listeners SIGINT exit=1 code=cancelled cleanup=complete inspect-web-event-listeners SIGTERM exit=1 code=cancelled cleanup=complete ``` As a published-package control, `inspect-web-network-capture` on a 90-byte HAR containing `{"log":{"version":"1.2","creator":{"name":"fixture","version":"1"},"entries":[]}}` was interrupted after its owned decoder subprocess appeared. It returned typed `cancelled` JSON with `cleanup: "complete"` and exited 130 for SIGINT and 143 for SIGTERM. This control used a tiny capture, not a large-memory workload. ## Evidence - Expected source: the current [shared cancellation helper](https://github.com/morluto/rea/blob/84a17d55199b1b46493aea6bf4e41029a3b200fc/src/cli/commandCancellation.ts#L8-L32), including its conventional-exit-code contract and signal mappings. - Failure source: the four package subprocess observations above; current [web runtime registrations](https://github.com/morluto/rea/blob/84a17d55199b1b46493aea6bf4e41029a3b200fc/src/cli/webRuntimeCommands.ts#L40-L80) place cancellation inside logging, and [the logger](https://github.com/morluto/rea/blob/84a17d55199b1b46493aea6bf4e41029a3b200fc/src/cliLogging.ts#L30-L50) subsequently assigns exit 1 for the returned typed error. - Evidence provenance: observed - Local verification: reproduced - Reproduction completeness: complete The [CLI status table](https://github.com/morluto/rea/blob/84a17d55199b1b46493aea6bf4e41029a3b200fc/docs/cli.md#L162-L176) provides the broader qualification described above. The existing real-browser acceptance test checks nonzero status, so exit 1 satisfies that test without proving preservation of the helper's exact signal status. Open and closed issues and PRs were searched for both command names, SIGINT, cancellation, signal exit and exit codes. Closed issue #113 addresses nonzero operation failure status; merged PR #1098 concerns JavaScript cancellation. Neither reports this web-command status overwrite. ## Restoration check Repeat the same four command/signal cases. They should return the same typed cancelled document, close their processes and fixture connections, and exit 130 for SIGINT or 143 for SIGTERM. A normal nonsignal operation error should still exit 1. Preserve the existing real-browser cleanup assertions separately; this connection-setup fixture does not establish that guarantee. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: These two commands lose the signal-specific status selected by their cancellation boundary, so automation relying on that status classifies user interruption as ordinary failure. The result remains nonzero and contains an accurate cancellation document; no evidence loss or cleanup failure was observed. - Workaround status: available - Workaround basis: The observed JSON result consistently identifies `code: "cancelled"`, allowing callers to distinguish cancellation by parsing stdout instead of relying only on the exit code.
bsakaguchi 2026-10-15 18:24:45 +00:00
Author
Owner

@N0zoM1z0 commented on GitHub (Oct 9, 2026):

Fixed by 1d9b96a9. Verified the built CLI on Linux/Node 24.18.0: both observe-web-execution and inspect-web-event-listeners, interrupted after contacting a local discovery endpoint, return typed cancelled JSON with complete cleanup and preserve SIGINT=130 / SIGTERM=143 (four real subprocess cases, empty stderr). This covers the reported connection-setup failure. Main CI at 283c82f68c passed: https://github.com/morluto/rea/actions/runs/37862776952 .

<!-- gh-comment-id:6071688234 --> @N0zoM1z0 commented on GitHub (Oct 9, 2026): Fixed by 1d9b96a9. Verified the built CLI on Linux/Node 24.18.0: both `observe-web-execution` and `inspect-web-event-listeners`, interrupted after contacting a local discovery endpoint, return typed `cancelled` JSON with complete cleanup and preserve SIGINT=130 / SIGTERM=143 (four real subprocess cases, empty stderr). This covers the reported connection-setup failure. Main CI at 283c82f68ce49037a5e3ffdf0cada9208aef69f3 passed: https://github.com/morluto/rea/actions/runs/37862776952 .
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#399
No description provided.