[GH-ISSUE #1751] fix(hopper): failed one-shot analysis stays alive and holds its target lease #589

Closed
opened 2026-10-15 18:26:27 +00:00 by bsakaguchi · 0 comments
Owner

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

Summary

After an external Hopper startup failure, REA's one-shot CLI emits its final structured failure but does not terminate. Its referenced target-lease listener remains alive, so another command for the same target is rejected as already open in the failed session. This was observed after a real macOS Automation denial and reproduced with a disposable launcher that exits immediately without opening Hopper.

Runtime or environment

  • REA package: rea-agents@6.4.0; inspected release source.
  • Runtime: Node.js 26.10.0, macOS 27.0, arm64.
  • Real-provider baseline: Hopper bundle 6.1.0-demo, build 42.6295552, on a non-stock installation. The independent launcher-failure reproduction exercises REA's lifecycle without starting Hopper and does not depend on that installation's modifications.
  • Target: a disposable, valid arm64 Mach-O executable.
  • Area: CLI. Interface: CLI and Python bridge. Earliest failure stage: Cleanup, cancellation, or lifecycle.

Steps to reproduce

  1. Use the tested baseline and a valid native executable that Hopper admission accepts. The following boundary reproduction isolates the failed-helper condition without changing Automation settings, licensing, or any live Hopper document.
  2. Compile the disposable fixture and create a launcher that fails immediately.
work=$(mktemp -d /tmp/rea-hopper-repro.XXXXXX)
printf 'int main(void) { return 0; }\n' > "$work/probe.c"
xcrun clang -O0 "$work/probe.c" -o "$work/probe"
cat > "$work/failure-helper" <<'SH'
#!/bin/sh
printf 'Cannot launch Hopper!\nNot authorized to send Apple events to Hopper Disassembler.\n' >&2
exit 2
SH
chmod 700 "$work/failure-helper"
HOPPER_LAUNCHER_PATH="$work/failure-helper" \
  npx -y rea-agents@6.4.0 analyze "$work/probe" --provider hopper --json
  1. Observe that the command prints a final cleanup_incomplete result containing the helper's exit code and stderr, but does not return a shell status. In another terminal, verify that this command's Node process remains alive. The test helper cannot have opened a document.
  2. While that failed command remains alive, run analysis again on the same target with the same launcher configuration. It rejects the target as already open in Session A. Stop only the owned failed command to finish this reproduction; preserve unrelated Hopper processes and documents.

Expected behavior

The one-shot CLI should finish a failed operation with its structured diagnostics and exit status 1. A completed failure must not retain an unreachable live session indefinitely. Cleanup uncertainty and ownership restrictions must remain truthful, and termination must not close or kill unrelated GUI documents. After the owned failure is recovered and its startup cause is corrected, a new invocation must be able to analyze the same target.

Actual behavior

One real invocation failed when Hopper's helper exited 2 with Not authorized to send Apple events to Hopper Disassembler. REA emitted its final failure JSON, but its original Node process was still alive more than five minutes later. After Automation access was granted, a second invocation for the same target was rejected by the live owner.

code: cleanup_incomplete
primary launcher exit_code: 2
primary launcher stderr:
Cannot launch Hopper!
Not authorized to send Apple events to Hopper Disassembler.

retry code: provider_unavailable
This Hopper target is already open in REA session [REDACTED].

A read-only lease-socket check correlated the owner with the original still-running CLI process. The run and process identifiers are omitted. This is a live failed-session problem, not stale-owner recovery after process exit.

One independent failed-launcher probe emitted its failure and remained alive for the full 12-second observation. Its natural exit status was still unavailable when the probe sent SIGTERM only to its owned process group. Stopping the original real failed CLI released its lease. Subsequent real executable analysis succeeded after the startup permission was corrected. The input executable's digest remained unchanged.

Evidence

  • Expected source: docs/cli.md, opening paragraph and Output and exit status, describes each command as a one-shot session and failed operations as status 1; src/cli.ts identifies the CLI as one-shot. These are in the release source above.
  • Failure source: The real failed CLI remained alive more than five minutes after its final JSON and its live lease blocked retry. The independent failed-helper probe reproduced the same nontermination. src/hopper/HopperTargetLease.ts, tryAcquireLease, creates a referenced net.Server; src/hopper/HopperCleanup.ts, cleanupHopperSession, retains the lease when authenticated external-document shutdown is unconfirmed; src/application/DirectAnalysisCleanup.ts, withSessionCleanup, returns the completed failure; src/cliLogging.ts, logCliCommand, sets process.exitCode without settling the referenced listener.
  • Evidence provenance: observed
  • Local verification: reproduced
  • Reproduction completeness: complete

Restoration check

Run the failing-helper reproduction and the real startup-denial condition when safely available. Require the final structured error and a terminal nonzero status within a bounded observation, without an indefinitely referenced listener. Verify that cleanup uncertainty is still reported and unrelated Hopper documents survive. After the owned failure is recovered, replace the failing helper with the valid Hopper launcher and confirm that analysis of the same target succeeds. Retain coverage for a genuinely active session refusing competing opens; bypassing that protection would not restore the contract.

Impact

Shell scripts and agents waiting for command completion can wait indefinitely after receiving a final failure result. Retrying the affected target remains blocked until the failed CLI is stopped. Stopping only that owned process provides a verified practical workaround for the observed no-document-startup case. The report does not request weaker document ownership checks or indiscriminate GUI termination.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: A bounded Hopper startup failure prevents one-shot command completion and blocks the affected target while its failed owner stays alive.
  • Workaround status: available
  • Workaround basis: Stopping only the investigator-owned failed CLI released its lease, and a fresh real executable analysis completed after the startup cause was corrected. The deterministic helper creates no GUI document. For failures with partial GUI effects, inspect the task document before cleanup.
Originally created by @coygeek on GitHub (Oct 11, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/1751 ## Summary After an external Hopper startup failure, REA's one-shot CLI emits its final structured failure but does not terminate. Its referenced target-lease listener remains alive, so another command for the same target is rejected as already open in the failed session. This was observed after a real macOS Automation denial and reproduced with a disposable launcher that exits immediately without opening Hopper. ## Runtime or environment - REA package: `rea-agents@6.4.0`; inspected [release source](https://github.com/morluto/rea/tree/a56f7bb668ab23fec8d33f7af353ac7ed2fe1895). - Runtime: Node.js `26.10.0`, macOS `27.0`, arm64. - Real-provider baseline: Hopper bundle `6.1.0-demo`, build `42.6295552`, on a non-stock installation. The independent launcher-failure reproduction exercises REA's lifecycle without starting Hopper and does not depend on that installation's modifications. - Target: a disposable, valid arm64 Mach-O executable. - Area: CLI. Interface: CLI and Python bridge. Earliest failure stage: Cleanup, cancellation, or lifecycle. ## Steps to reproduce 1. Use the tested baseline and a valid native executable that Hopper admission accepts. The following boundary reproduction isolates the failed-helper condition without changing Automation settings, licensing, or any live Hopper document. 2. Compile the disposable fixture and create a launcher that fails immediately. ```sh work=$(mktemp -d /tmp/rea-hopper-repro.XXXXXX) printf 'int main(void) { return 0; }\n' > "$work/probe.c" xcrun clang -O0 "$work/probe.c" -o "$work/probe" cat > "$work/failure-helper" <<'SH' #!/bin/sh printf 'Cannot launch Hopper!\nNot authorized to send Apple events to Hopper Disassembler.\n' >&2 exit 2 SH chmod 700 "$work/failure-helper" HOPPER_LAUNCHER_PATH="$work/failure-helper" \ npx -y rea-agents@6.4.0 analyze "$work/probe" --provider hopper --json ``` 3. Observe that the command prints a final `cleanup_incomplete` result containing the helper's exit code and stderr, but does not return a shell status. In another terminal, verify that this command's Node process remains alive. The test helper cannot have opened a document. 4. While that failed command remains alive, run analysis again on the same target with the same launcher configuration. It rejects the target as already open in Session A. Stop only the owned failed command to finish this reproduction; preserve unrelated Hopper processes and documents. ## Expected behavior The one-shot CLI should finish a failed operation with its structured diagnostics and exit status `1`. A completed failure must not retain an unreachable live session indefinitely. Cleanup uncertainty and ownership restrictions must remain truthful, and termination must not close or kill unrelated GUI documents. After the owned failure is recovered and its startup cause is corrected, a new invocation must be able to analyze the same target. ## Actual behavior One real invocation failed when Hopper's helper exited `2` with `Not authorized to send Apple events to Hopper Disassembler.` REA emitted its final failure JSON, but its original Node process was still alive more than five minutes later. After Automation access was granted, a second invocation for the same target was rejected by the live owner. ```text code: cleanup_incomplete primary launcher exit_code: 2 primary launcher stderr: Cannot launch Hopper! Not authorized to send Apple events to Hopper Disassembler. retry code: provider_unavailable This Hopper target is already open in REA session [REDACTED]. ``` A read-only lease-socket check correlated the owner with the original still-running CLI process. The run and process identifiers are omitted. This is a live failed-session problem, not stale-owner recovery after process exit. One independent failed-launcher probe emitted its failure and remained alive for the full 12-second observation. Its natural exit status was still unavailable when the probe sent SIGTERM only to its owned process group. Stopping the original real failed CLI released its lease. Subsequent real executable analysis succeeded after the startup permission was corrected. The input executable's digest remained unchanged. ## Evidence - Expected source: `docs/cli.md`, opening paragraph and `Output and exit status`, describes each command as a one-shot session and failed operations as status `1`; `src/cli.ts` identifies the CLI as one-shot. These are in the release source above. - Failure source: The real failed CLI remained alive more than five minutes after its final JSON and its live lease blocked retry. The independent failed-helper probe reproduced the same nontermination. `src/hopper/HopperTargetLease.ts`, `tryAcquireLease`, creates a referenced `net.Server`; `src/hopper/HopperCleanup.ts`, `cleanupHopperSession`, retains the lease when authenticated external-document shutdown is unconfirmed; `src/application/DirectAnalysisCleanup.ts`, `withSessionCleanup`, returns the completed failure; `src/cliLogging.ts`, `logCliCommand`, sets `process.exitCode` without settling the referenced listener. - Evidence provenance: observed - Local verification: reproduced - Reproduction completeness: complete ## Restoration check Run the failing-helper reproduction and the real startup-denial condition when safely available. Require the final structured error and a terminal nonzero status within a bounded observation, without an indefinitely referenced listener. Verify that cleanup uncertainty is still reported and unrelated Hopper documents survive. After the owned failure is recovered, replace the failing helper with the valid Hopper launcher and confirm that analysis of the same target succeeds. Retain coverage for a genuinely active session refusing competing opens; bypassing that protection would not restore the contract. ## Impact Shell scripts and agents waiting for command completion can wait indefinitely after receiving a final failure result. Retrying the affected target remains blocked until the failed CLI is stopped. Stopping only that owned process provides a verified practical workaround for the observed no-document-startup case. The report does not request weaker document ownership checks or indiscriminate GUI termination. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: A bounded Hopper startup failure prevents one-shot command completion and blocks the affected target while its failed owner stays alive. - Workaround status: available - Workaround basis: Stopping only the investigator-owned failed CLI released its lease, and a fresh real executable analysis completed after the startup cause was corrected. The deterministic helper creates no GUI document. For failures with partial GUI effects, inspect the task document before cleanup.
bsakaguchi 2026-10-15 18:26:27 +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#589
No description provided.