[GH-ISSUE #340] Verify Hopper cancellation, timeout, mutation readback, and shutdown #187

Open
opened 2026-10-15 18:22:14 +00:00 by bsakaguchi · 2 comments
Owner

Originally created by @morluto on GitHub (Jul 20, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/340

Run real-Hopper verifier tests for cancellation, timeout, database save/reopen mutation readback, and clean shutdown.

Acceptance:

  • Cancellation and timeout stop analysis without leaks.
  • Database save, close, reopen, and readback preserve names/comments/bookmarks.
  • Owned shutdown completes and does not terminate unrelated Hopper instances.

Part of #25.

Originally created by @morluto on GitHub (Jul 20, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/340 Run real-Hopper verifier tests for cancellation, timeout, database save/reopen mutation readback, and clean shutdown. Acceptance: - Cancellation and timeout stop analysis without leaks. - Database save, close, reopen, and readback preserve names/comments/bookmarks. - Owned shutdown completes and does not terminate unrelated Hopper instances. Part of #25.
Author
Owner

@LuisTaffelli commented on GitHub (Oct 11, 2026):

Partial Linux recovery and cleanup evidence from #1942, pinned to tested commit 8ea0432ffc11d47d2f43fbd4964f48116b4bd4a5.

Environment: Omarchy 4.0.4 x86-64, official Hopper 6.4.2-1 demo, Node 24.18.0/npm 11.16.0. The source CLI/MCP verifier's local VM execution is 45930d25-d485-4ff3-a0bf-0e6286f12329, completed 2026-10-11T21:34:14.401Z; retained report current-verification/hopper-current-verifier.log. Execution details and artifact identities are summarized in #1942. This was a local real-provider run, separate from GitHub CI.

Observed results:

  • Caller-cancellation recovery and pathological-regex deadline/cancellation checks were followed by successful requests in the same session.
  • Competing Linux launches for the same and a different target were rejected while preserving the owning session's documents.
  • Target switching and close disposed native documents; shutdown completed and an unrelated Hopper instance survived.
  • The dedicated clean-runner process observer passed after teardown. Independent doctor, CLI analysis, modified-launcher rejection and setup-idempotence postchecks also passed; their separate verifier execution was e75897f6-f064-47df-ac2b-6bd85b796cb2, completed 2026-10-11T21:34:28.004Z.

Separate OS integration regressions detect reparented helpers and a native executable launched through a subsequently removed alternate symlink, without signaling those fixtures. Both cases passed in CI run 38176869885, on the same commit. These fixtures prove process-observation behavior, not real-Hopper behavior; they do not authorize signaling or establish ownership from names/paths.

Limits: successful cancellation recovery does not prove interruption of synchronous Hopper engine work. Database save/close/reopen mutation persistence remains unverified, as do macOS/other Hopper builds, licensed-provider scope and packaged npm-artifact behavior. The uninterrupted clean-installation lane remains unverified: repeat vendor downloads timed out, and the native run used the previously downloaded official archive after exact-size/pinned-hash verification. Later reporting-only commit 97b76aa2 was not rerun with the real provider. This evidence does not satisfy all acceptance criteria or close the issue.

Overall CI is not claimed green. The latest PR run still reported failing Qwen configuration and browser response-shape tests; their relationship to this PR has not been established by this investigation. Those failures are not presented here as unrelated or expected.

<!-- gh-comment-id:6114325797 --> @LuisTaffelli commented on GitHub (Oct 11, 2026): Partial Linux recovery and cleanup evidence from #1942, pinned to tested commit [`8ea0432ffc11d47d2f43fbd4964f48116b4bd4a5`](https://github.com/LuisTaffelli/rea/commit/8ea0432ffc11d47d2f43fbd4964f48116b4bd4a5). Environment: Omarchy 4.0.4 x86-64, official Hopper 6.4.2-1 demo, Node 24.18.0/npm 11.16.0. The source CLI/MCP verifier's local VM execution is `45930d25-d485-4ff3-a0bf-0e6286f12329`, completed `2026-10-11T21:34:14.401Z`; retained report `current-verification/hopper-current-verifier.log`. Execution details and artifact identities are summarized in #1942. This was a local real-provider run, separate from GitHub CI. Observed results: - Caller-cancellation recovery and pathological-regex deadline/cancellation checks were followed by successful requests in the same session. - Competing Linux launches for the same and a different target were rejected while preserving the owning session's documents. - Target switching and close disposed native documents; shutdown completed and an unrelated Hopper instance survived. - The dedicated clean-runner process observer passed after teardown. Independent doctor, CLI analysis, modified-launcher rejection and setup-idempotence postchecks also passed; their separate verifier execution was `e75897f6-f064-47df-ac2b-6bd85b796cb2`, completed `2026-10-11T21:34:28.004Z`. Separate OS integration regressions detect reparented helpers and a native executable launched through a subsequently removed alternate symlink, without signaling those fixtures. Both cases passed in [CI run 38176869885](https://github.com/morluto/rea/actions/runs/38176869885), on the same commit. These fixtures prove process-observation behavior, not real-Hopper behavior; they do not authorize signaling or establish ownership from names/paths. Limits: successful cancellation recovery does not prove interruption of synchronous Hopper engine work. Database save/close/reopen mutation persistence remains unverified, as do macOS/other Hopper builds, licensed-provider scope and packaged npm-artifact behavior. The uninterrupted clean-installation lane remains unverified: repeat vendor downloads timed out, and the native run used the previously downloaded official archive after exact-size/pinned-hash verification. Later reporting-only commit `97b76aa2` was not rerun with the real provider. This evidence does not satisfy all acceptance criteria or close the issue. Overall CI is not claimed green. The latest PR run still reported failing Qwen configuration and browser response-shape tests; their relationship to this PR has not been established by this investigation. Those failures are not presented here as unrelated or expected.
Author
Owner

@dogskip commented on GitHub (Oct 13, 2026):

macOS real-Hopper data for the cancellation and leak criteria

The macOS Hopper demo gives a reproducible way to hold a bridge request without a reply. About 30 minutes after launch, Hopper Disassembler 6.6.0-demo shows a modal alert, "Demo period has expired. Document will be closed.", whose only button is OK. A request that reached Hopper while the alert was up got no reply while the alert stayed up (observed for about 2 minutes, until close_binary). I used that state to exercise client cancellation over MCP.

Environment: main at ebaaa79ca (npm ci --ignore-scripts && npm run compile), macOS 27.2 (26B5101f) arm64, Node.js 26.0.0, Hopper Disassembler 6.6.0-demo (build 42.6316032). A script drove rea mcp over stdio against a small arm64 Mach-O, and nothing touched the alert until the last step.

After open_binary with provider_id: "hopper" and one analyze_function, the script waited for the alert. Both runs gave these results:

step result
analyze_function on a function not analyzed earlier; no reply in 60 s; the client sends notifications/cancelled no reply afterwards, as the spec asks
binary_session returns at once; analysis_activity is busy, with analyze_function at caller_state: "cancelled"
a second analyze_function; no reply in 30 s; cancelled too no reply afterwards
close_binary cleanup_incomplete at once; hopper-document reads "bridge operation analyze_function remained active after caller cancelled", the runtime root is retained, and active_request.caller_state is "cancelled"
open_binary with provider_id: "ghidra" the same cleanup_incomplete, at once
binary_session analysis_activity is not_observed, with an empty providers list, although the cleanup error still names the active request

The second of those two runs, which also captured stderr, continued as follows. The other run's server was still running 60 s after EOF, and that run did not wait longer.

  • On stdin EOF, the server wrote "REA could not close cleanly. End the REA process before starting it again." to stderr twice. It was still running 600 s later.
  • OK on the alert quit Hopper. When the script checked right after Hopper quit, about a second after OK, the server had already exited. No exact exit time or exit code was recorded. The runtime root stayed in /tmp after both processes had exited. It holds bootstrap.py with the bridge token, bridge.sock and ownership.json, and its mode is 0700.

In an earlier run the client did not cancel the second request. There, close_binary and the Ghidra open_binary each got no reply within 180 s, because close first waits for earlier calls (BinarySession.ts#L361-L363) and the open waits behind that close.

How this reads against the acceptance criteria:

  • Cancellation returns control, and the session reports the abandoned work until close_binary fails, as installation.md#L418-L419 describes.
  • While Hopper cannot serve the bridge, cancellation does not end the bridge operation: it stays active with caller_state: "cancelled". Later Hopper requests on the session wait behind it with no deadline; the queue sets one only when given a timeout (HopperRequestQueue.ts#L124-L127).
  • Timeout was not exercised: MCP analysis calls carry no deadline.
  • Cleanup fails closed and names the cause. The owned launcher's shutdown was verified (owned-shutdown with verified-cleanup in stderr); what stays unconfirmed is the external app's document. The server does not exit on EOF until Hopper does, and the runtime root remains afterwards. If the client keeps the session instead, pressing OK leads to the state in #2975, which was shown there without a cancelled request.
  • Database save, reopen and readback are not covered here.

This comes from one machine: 2 runs for the table and for the server outliving EOF (60 s and 600 s observed), and 1 run each for the other shutdown lines and for the uncancelled-request paragraph.

<!-- gh-comment-id:6157005833 --> @dogskip commented on GitHub (Oct 13, 2026): **macOS real-Hopper data for the cancellation and leak criteria** The macOS Hopper demo gives a reproducible way to hold a bridge request without a reply. About 30 minutes after launch, Hopper Disassembler 6.6.0-demo shows a modal alert, "Demo period has expired. Document will be closed.", whose only button is OK. A request that reached Hopper while the alert was up got no reply while the alert stayed up (observed for about 2 minutes, until `close_binary`). I used that state to exercise client cancellation over MCP. Environment: main at ebaaa79ca (`npm ci --ignore-scripts && npm run compile`), macOS 27.2 (26B5101f) arm64, Node.js 26.0.0, Hopper Disassembler 6.6.0-demo (build 42.6316032). A script drove `rea mcp` over stdio against a small arm64 Mach-O, and nothing touched the alert until the last step. After `open_binary` with `provider_id: "hopper"` and one `analyze_function`, the script waited for the alert. Both runs gave these results: | step | result | | --- | --- | | `analyze_function` on a function not analyzed earlier; no reply in 60 s; the client sends `notifications/cancelled` | no reply afterwards, as the spec asks | | `binary_session` | returns at once; `analysis_activity` is `busy`, with `analyze_function` at `caller_state: "cancelled"` | | a second `analyze_function`; no reply in 30 s; cancelled too | no reply afterwards | | `close_binary` | `cleanup_incomplete` at once; `hopper-document` reads "bridge operation analyze_function remained active after caller cancelled", the runtime root is retained, and `active_request.caller_state` is `"cancelled"` | | `open_binary` with `provider_id: "ghidra"` | the same `cleanup_incomplete`, at once | | `binary_session` | `analysis_activity` is `not_observed`, with an empty `providers` list, although the cleanup error still names the active request | The second of those two runs, which also captured stderr, continued as follows. The other run's server was still running 60 s after EOF, and that run did not wait longer. - On stdin EOF, the server wrote "REA could not close cleanly. End the REA process before starting it again." to stderr twice. It was still running 600 s later. - OK on the alert quit Hopper. When the script checked right after Hopper quit, about a second after OK, the server had already exited. No exact exit time or exit code was recorded. The runtime root stayed in `/tmp` after both processes had exited. It holds `bootstrap.py` with the bridge token, `bridge.sock` and `ownership.json`, and its mode is 0700. In an earlier run the client did not cancel the second request. There, `close_binary` and the Ghidra `open_binary` each got no reply within 180 s, because `close` first waits for earlier calls ([BinarySession.ts#L361-L363](https://github.com/morluto/rea/blob/ebaaa79ca02bde4a73013a2a9a781d0b07a57ee4/src/application/binary/BinarySession.ts#L361-L363)) and the open waits behind that close. How this reads against the acceptance criteria: - Cancellation returns control, and the session reports the abandoned work until `close_binary` fails, as [installation.md#L418-L419](https://github.com/morluto/rea/blob/ebaaa79ca02bde4a73013a2a9a781d0b07a57ee4/docs/installation.md#L418-L419) describes. - While Hopper cannot serve the bridge, cancellation does not end the bridge operation: it stays active with `caller_state: "cancelled"`. Later Hopper requests on the session wait behind it with no deadline; the queue sets one only when given a timeout ([HopperRequestQueue.ts#L124-L127](https://github.com/morluto/rea/blob/ebaaa79ca02bde4a73013a2a9a781d0b07a57ee4/src/hopper/HopperRequestQueue.ts#L124-L127)). - Timeout was not exercised: MCP analysis calls carry no deadline. - Cleanup fails closed and names the cause. The owned launcher's shutdown was verified (`owned-shutdown` with `verified-cleanup` in stderr); what stays unconfirmed is the external app's document. The server does not exit on EOF until Hopper does, and the runtime root remains afterwards. If the client keeps the session instead, pressing OK leads to the state in #2975, which was shown there without a cancelled request. - Database save, reopen and readback are not covered here. This comes from one machine: 2 runs for the table and for the server outliving EOF (60 s and 600 s observed), and 1 run each for the other shutdown lines and for the uncancelled-request paragraph.
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#187
No description provided.