[GH-ISSUE #3054] [Bug] macOS: doctor passes a Hopper launcher in ~/Applications that CLI analysis does not use #1066

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

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

Before submitting

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

Area

Provider or capability discovery

Observed behavior

On macOS, rea doctor --provider hopper --json reports healthy: true when Hopper exists only at ~/Applications/Hopper Disassembler.app, the location REA's own macOS installer uses, but rea analyze --provider hopper in the same environment rejects Hopper with executable_missing for the fixed default /Applications/Hopper Disassembler.app/Contents/MacOS/hopper. The documented readiness check therefore passes for the engine the CLI then reports unavailable, and the CLI's own remediation points back at that passing doctor check. MCP registrations written by rea setup carry HOPPER_LAUNCHER_PATH and are not affected.

Observed on both builds with identical results. Step 3 exits 0 with healthy: true, and its only Hopper scope check passes for the ~/Applications stub (HOME shown as $HOME):

{"name":"hopper","ok":true,"classification":"healthy","detail":"$HOME/Applications/Hopper Disassembler.app/Contents/MacOS/hopper"}

Step 4 exits 1 without launching anything:

{"error":"Analysis failed","code":"provider_unavailable","message":"The selected analysis provider is unavailable. Review the reported local diagnostics or run `rea doctor`, then retry.","details":{"requested_provider_id":"hopper","rejections":[{"provider_id":"hopper","code":"executable_missing","reason":"Hopper launcher is missing or not executable: /Applications/Hopper Disassembler.app/Contents/MacOS/hopper"}]}}

(Output trimmed to the Hopper fields; the Ghidra and IDA rejections are not_configured.) Step 5 exits 0 and reports Hopper "status":"unavailable", "code":"executable_missing" with launcher_path /Applications/Hopper Disassembler.app/Contents/MacOS/hopper. Step 6 reports Hopper "status":"available" with launcher_path set to the ~/Applications stub. Control: without the sandbox, the same doctor command on both builds reports the default /Applications/... launcher as healthy, so doctor and runtime agree whenever the default copy exists. The renamed Hopper*.app in /Applications and the Homebrew cask locations that doctor also searches were not exercised, and no rea analyze was run with HOPPER_LAUNCHER_PATH set, because that would launch the stub bundle.

Reproduction

Executed on both builds, on a Mac that also has Hopper at the default path; the sandbox hides that copy from the REA process. Not executed: on a Mac whose only Hopper is ~/Applications/Hopper Disassembler.app in the user's real HOME (for example after the macOS installer in rea setup), steps 3 to 5 without sandbox-exec and without step 1 should show the same result.

  1. In an empty HOME, create a stub launcher at the location the macOS installer uses. Doctor only checks that it is executable and does not run it on macOS.
export HOME="$(mktemp -d)"; cd "$HOME"; unset HOPPER_LAUNCHER_PATH
mkdir -p "$HOME/Applications/Hopper Disassembler.app/Contents/MacOS"
printf '#!/bin/sh\nexit 97\n' > "$HOME/Applications/Hopper Disassembler.app/Contents/MacOS/hopper"
chmod +x "$HOME/Applications/Hopper Disassembler.app/Contents/MacOS/hopper"
lipo /bin/ls -thin arm64e -output "$HOME/ls-arm64e"
  1. Save this profile as hide-hopper.sb. It denies the REA process access to the default /Applications copy only (access(X_OK) then fails with EPERM); nothing on disk changes.
(version 1)
(allow default)
(deny file-read* file-read-metadata process-exec (subpath "/Applications/Hopper Disassembler.app"))
  1. Run sandbox-exec -f hide-hopper.sb rea doctor --provider hopper --json.
  2. Run sandbox-exec -f hide-hopper.sb rea analyze "$HOME/ls-arm64e" --provider hopper --json.
  3. Run sandbox-exec -f hide-hopper.sb rea providers --json and read the Hopper entry's availability in analysis_provider_candidates. This lists runtime provider discovery without launching anything.
  4. Repeat step 5 with HOPPER_LAUNCHER_PATH exported to the stub $HOME/Applications/Hopper Disassembler.app/Contents/MacOS/hopper.

Expected behavior

docs/installation.md ("Check readiness for your task", lines 139-150 on the main build) names rea doctor --provider hopper --json as the readiness check for one analysis engine, with its scope_checks deciding healthy and the exit status. docs/cli.md (lines 153-157) tells a CLI user who receives provider_unavailable to run rea doctor --provider ID --json to diagnose the selected engine. docs/installation.md ("Launcher paths and troubleshooting", lines 369-376) states that on macOS REA uses /Applications/Hopper Disassembler.app/Contents/MacOS/hopper by default and that HOPPER_LAUNCHER_PATH overrides it. In this state the Hopper readiness check should therefore not report healthy for a launcher the CLI will not use: either doctor reports Hopper not ready for this environment and names the found launcher and HOPPER_LAUNCHER_PATH, or the CLI runtime uses the same launcher doctor found.

Impact and scope

Affected: macOS CLI users whose only Hopper is in ~/Applications (where REA's own macOS installer puts it), or in another location doctor searches but the runtime does not use, with HOPPER_LAUNCHER_PATH unset in the shell. The Hopper readiness check passes, while every CLI Hopper analysis fails with executable_missing, and the CLI remediation sends the user back to that passing check. Not affected: MCP sessions started from registrations written by rea setup, which carry HOPPER_LAUNCHER_PATH; Linux, where doctor and the runtime use the same paths; hosts with Hopper at the default /Applications path. No evidence-integrity, isolation or recovery impact. Workaround: export HOPPER_LAUNCHER_PATH to the ~/Applications launcher, as docs/installation.md documents (step 6).

REA version

6.4.0 (npm rea-agents), and main at e049ef70f7 built from source

Runtime and platform

Node 26.10.0, npm 12.2.0; macOS 27.0 (26A428) arm64; Hopper Disassembler 6.1.0 demo at the default path, hidden from REA by sandbox-exec for the reproduction

Target and artifact context

Thin arm64e slice of /bin/ls (Mach-O executable); the failure occurs before any target is opened

Interface

CLI

Earliest failure stage

Setup or provider admission

Relevant output or evidence

rea doctor --provider hopper --json  -> exit 0, healthy: true
  {"name":"hopper","ok":true,"classification":"healthy","detail":"$HOME/Applications/Hopper Disassembler.app/Contents/MacOS/hopper"}
rea analyze $HOME/ls-arm64e --provider hopper --json  -> exit 1
  provider_unavailable; hopper: executable_missing "Hopper launcher is missing or not executable: /Applications/Hopper Disassembler.app/Contents/MacOS/hopper"
rea providers --json  -> hopper availability: unavailable / executable_missing, launcher_path /Applications/Hopper Disassembler.app/Contents/MacOS/hopper
HOPPER_LAUNCHER_PATH=$HOME/Applications/.../hopper rea providers --json  -> hopper availability: available

Typed outcome or recovery expected

In this state, rea doctor --provider hopper --json should not report the Hopper scope check healthy for a launcher the CLI runtime will not use. Either the check fails (or warns) and names the found launcher with HOPPER_LAUNCHER_PATH as the remediation, or the runtime resolves the same launcher doctor found.

Failing now: with HOME containing only ~/Applications/Hopper Disassembler.app, the default copy absent or hidden, and HOPPER_LAUNCHER_PATH unset, step 3 reports healthy: true while steps 4 and 5 report executable_missing for the default path. Restored: in that state step 3 agrees with steps 4 and 5, either because doctor reports Hopper not ready and names the found launcher with HOPPER_LAUNCHER_PATH, or because the runtime resolves the same launcher doctor reported. Control: with Hopper at the default /Applications path, or with HOPPER_LAUNCHER_PATH set to the ~/Applications launcher (step 6), doctor stays healthy and runtime discovery reports that launcher available.

Analysis or suspected cause

Source-derived, not a prescribed fix. On the main build, findHopper in src/application/DoctorDiagnostics.ts (lines 146-164) accepts the default path, the Linux paths, every Hopper*.app under /Applications and ~/Applications (manualHopperPaths, src/application/Doctor.ts lines 576-590), then the Homebrew cask, while defaultHopperLauncherPath in src/config/parseConfig.ts (lines 24-33, used at lines 97-102) returns the fixed default on every non-Linux platform.

The macOS installer's location is stated in docs/installation.md (macOS setup paragraph) and src/application/MacHopper.ts line 324. rea setup copies doctor's found path into client registrations as HOPPER_LAUNCHER_PATH (src/application/SetupHost.ts lines 158-171), which is why MCP sessions started from those registrations are unaffected. The default-path copy was hidden with sandbox-exec rather than absent; a Mac with no default-path copy was not available.

Additional context

Related, not duplicates: #1183 (closed; doctor and setup accepted a relative GHIDRA_INSTALL_DIR the server rejected), #2785 (closed; Ghidra doctor passed while analysis failed under Rosetta), and #3016 (open; persist provider settings such as HOPPER_LAUNCHER_PATH in a REA config).

Originally created by @coygeek on GitHub (Oct 14, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3054 ### 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 Provider or capability discovery ### Observed behavior On macOS, `rea doctor --provider hopper --json` reports `healthy: true` when Hopper exists only at `~/Applications/Hopper Disassembler.app`, the location REA's own macOS installer uses, but `rea analyze --provider hopper` in the same environment rejects Hopper with `executable_missing` for the fixed default `/Applications/Hopper Disassembler.app/Contents/MacOS/hopper`. The documented readiness check therefore passes for the engine the CLI then reports unavailable, and the CLI's own remediation points back at that passing doctor check. MCP registrations written by `rea setup` carry `HOPPER_LAUNCHER_PATH` and are not affected. Observed on both builds with identical results. Step 3 exits 0 with `healthy: true`, and its only Hopper scope check passes for the `~/Applications` stub (HOME shown as `$HOME`): ```json {"name":"hopper","ok":true,"classification":"healthy","detail":"$HOME/Applications/Hopper Disassembler.app/Contents/MacOS/hopper"} ``` Step 4 exits 1 without launching anything: ```json {"error":"Analysis failed","code":"provider_unavailable","message":"The selected analysis provider is unavailable. Review the reported local diagnostics or run `rea doctor`, then retry.","details":{"requested_provider_id":"hopper","rejections":[{"provider_id":"hopper","code":"executable_missing","reason":"Hopper launcher is missing or not executable: /Applications/Hopper Disassembler.app/Contents/MacOS/hopper"}]}} ``` (Output trimmed to the Hopper fields; the Ghidra and IDA rejections are `not_configured`.) Step 5 exits 0 and reports Hopper `"status":"unavailable"`, `"code":"executable_missing"` with `launcher_path` `/Applications/Hopper Disassembler.app/Contents/MacOS/hopper`. Step 6 reports Hopper `"status":"available"` with `launcher_path` set to the `~/Applications` stub. Control: without the sandbox, the same doctor command on both builds reports the default `/Applications/...` launcher as healthy, so doctor and runtime agree whenever the default copy exists. The renamed `Hopper*.app` in `/Applications` and the Homebrew cask locations that doctor also searches were not exercised, and no `rea analyze` was run with `HOPPER_LAUNCHER_PATH` set, because that would launch the stub bundle. ### Reproduction Executed on both builds, on a Mac that also has Hopper at the default path; the sandbox hides that copy from the REA process. Not executed: on a Mac whose only Hopper is `~/Applications/Hopper Disassembler.app` in the user's real HOME (for example after the macOS installer in `rea setup`), steps 3 to 5 without `sandbox-exec` and without step 1 should show the same result. 1. In an empty HOME, create a stub launcher at the location the macOS installer uses. Doctor only checks that it is executable and does not run it on macOS. ```sh export HOME="$(mktemp -d)"; cd "$HOME"; unset HOPPER_LAUNCHER_PATH mkdir -p "$HOME/Applications/Hopper Disassembler.app/Contents/MacOS" printf '#!/bin/sh\nexit 97\n' > "$HOME/Applications/Hopper Disassembler.app/Contents/MacOS/hopper" chmod +x "$HOME/Applications/Hopper Disassembler.app/Contents/MacOS/hopper" lipo /bin/ls -thin arm64e -output "$HOME/ls-arm64e" ``` 2. Save this profile as `hide-hopper.sb`. It denies the REA process access to the default `/Applications` copy only (`access(X_OK)` then fails with `EPERM`); nothing on disk changes. ```scheme (version 1) (allow default) (deny file-read* file-read-metadata process-exec (subpath "/Applications/Hopper Disassembler.app")) ``` 3. Run `sandbox-exec -f hide-hopper.sb rea doctor --provider hopper --json`. 4. Run `sandbox-exec -f hide-hopper.sb rea analyze "$HOME/ls-arm64e" --provider hopper --json`. 5. Run `sandbox-exec -f hide-hopper.sb rea providers --json` and read the Hopper entry's `availability` in `analysis_provider_candidates`. This lists runtime provider discovery without launching anything. 6. Repeat step 5 with `HOPPER_LAUNCHER_PATH` exported to the stub `$HOME/Applications/Hopper Disassembler.app/Contents/MacOS/hopper`. ### Expected behavior `docs/installation.md` ("Check readiness for your task", lines 139-150 on the `main` build) names `rea doctor --provider hopper --json` as the readiness check for one analysis engine, with its `scope_checks` deciding `healthy` and the exit status. `docs/cli.md` (lines 153-157) tells a CLI user who receives `provider_unavailable` to run `rea doctor --provider ID --json` to diagnose the selected engine. `docs/installation.md` ("Launcher paths and troubleshooting", lines 369-376) states that on macOS REA uses `/Applications/Hopper Disassembler.app/Contents/MacOS/hopper` by default and that `HOPPER_LAUNCHER_PATH` overrides it. In this state the Hopper readiness check should therefore not report healthy for a launcher the CLI will not use: either doctor reports Hopper not ready for this environment and names the found launcher and `HOPPER_LAUNCHER_PATH`, or the CLI runtime uses the same launcher doctor found. ### Impact and scope Affected: macOS CLI users whose only Hopper is in `~/Applications` (where REA's own macOS installer puts it), or in another location doctor searches but the runtime does not use, with `HOPPER_LAUNCHER_PATH` unset in the shell. The Hopper readiness check passes, while every CLI Hopper analysis fails with `executable_missing`, and the CLI remediation sends the user back to that passing check. Not affected: MCP sessions started from registrations written by `rea setup`, which carry `HOPPER_LAUNCHER_PATH`; Linux, where doctor and the runtime use the same paths; hosts with Hopper at the default `/Applications` path. No evidence-integrity, isolation or recovery impact. Workaround: export `HOPPER_LAUNCHER_PATH` to the `~/Applications` launcher, as `docs/installation.md` documents (step 6). ### REA version 6.4.0 (npm `rea-agents`), and `main` at e049ef70f71aeb9965e9d75d4d4a84ea02eaee11 built from source ### Runtime and platform Node 26.10.0, npm 12.2.0; macOS 27.0 (26A428) arm64; Hopper Disassembler 6.1.0 demo at the default path, hidden from REA by `sandbox-exec` for the reproduction ### Target and artifact context Thin arm64e slice of `/bin/ls` (Mach-O executable); the failure occurs before any target is opened ### Interface CLI ### Earliest failure stage Setup or provider admission ### Relevant output or evidence ```text rea doctor --provider hopper --json -> exit 0, healthy: true {"name":"hopper","ok":true,"classification":"healthy","detail":"$HOME/Applications/Hopper Disassembler.app/Contents/MacOS/hopper"} rea analyze $HOME/ls-arm64e --provider hopper --json -> exit 1 provider_unavailable; hopper: executable_missing "Hopper launcher is missing or not executable: /Applications/Hopper Disassembler.app/Contents/MacOS/hopper" rea providers --json -> hopper availability: unavailable / executable_missing, launcher_path /Applications/Hopper Disassembler.app/Contents/MacOS/hopper HOPPER_LAUNCHER_PATH=$HOME/Applications/.../hopper rea providers --json -> hopper availability: available ``` ### Typed outcome or recovery expected In this state, `rea doctor --provider hopper --json` should not report the Hopper scope check healthy for a launcher the CLI runtime will not use. Either the check fails (or warns) and names the found launcher with `HOPPER_LAUNCHER_PATH` as the remediation, or the runtime resolves the same launcher doctor found. Failing now: with HOME containing only `~/Applications/Hopper Disassembler.app`, the default copy absent or hidden, and `HOPPER_LAUNCHER_PATH` unset, step 3 reports `healthy: true` while steps 4 and 5 report `executable_missing` for the default path. Restored: in that state step 3 agrees with steps 4 and 5, either because doctor reports Hopper not ready and names the found launcher with `HOPPER_LAUNCHER_PATH`, or because the runtime resolves the same launcher doctor reported. Control: with Hopper at the default `/Applications` path, or with `HOPPER_LAUNCHER_PATH` set to the `~/Applications` launcher (step 6), doctor stays healthy and runtime discovery reports that launcher available. ### Analysis or suspected cause Source-derived, not a prescribed fix. On the `main` build, `findHopper` in `src/application/DoctorDiagnostics.ts` (lines 146-164) accepts the default path, the Linux paths, every `Hopper*.app` under `/Applications` and `~/Applications` (`manualHopperPaths`, `src/application/Doctor.ts` lines 576-590), then the Homebrew cask, while `defaultHopperLauncherPath` in `src/config/parseConfig.ts` (lines 24-33, used at lines 97-102) returns the fixed default on every non-Linux platform. The macOS installer's location is stated in `docs/installation.md` (macOS setup paragraph) and `src/application/MacHopper.ts` line 324. `rea setup` copies doctor's found path into client registrations as `HOPPER_LAUNCHER_PATH` (`src/application/SetupHost.ts` lines 158-171), which is why MCP sessions started from those registrations are unaffected. The default-path copy was hidden with `sandbox-exec` rather than absent; a Mac with no default-path copy was not available. ### Additional context Related, not duplicates: #1183 (closed; doctor and setup accepted a relative `GHIDRA_INSTALL_DIR` the server rejected), #2785 (closed; Ghidra doctor passed while analysis failed under Rosetta), and #3016 (open; persist provider settings such as `HOPPER_LAUNCHER_PATH` in a REA config).
bsakaguchi 2026-10-15 18:30:47 +00:00
  • closed this issue
  • added the
    bug
    label
Author
Owner

@morluto commented on GitHub (Oct 14, 2026):

Superseded by published main commit 7582f0dff2. Runtime configuration and doctor now share MacHopperPaths: runtime checks the canonical system and user installer locations, then renamed Hopper bundles; explicit HOPPER_LAUNCHER_PATH is preserved. Homebrew cask discovery remains doctor-only and the installation guide explains explicit runtime configuration. Validation includes MacHopperPaths.test.ts, config default-path tests, Doctor user/renamed/Homebrew discovery tests, and the public explicit-path/no-launch doctor boundary test. This does not claim real Hopper launch or a macOS host run.

<!-- gh-comment-id:6165002404 --> @morluto commented on GitHub (Oct 14, 2026): Superseded by published main commit 7582f0dff29ae0e80c97b885ddd386ac61a92570. Runtime configuration and doctor now share `MacHopperPaths`: runtime checks the canonical system and user installer locations, then renamed Hopper bundles; explicit `HOPPER_LAUNCHER_PATH` is preserved. Homebrew cask discovery remains doctor-only and the installation guide explains explicit runtime configuration. Validation includes `MacHopperPaths.test.ts`, config default-path tests, Doctor user/renamed/Homebrew discovery tests, and the public explicit-path/no-launch doctor boundary test. This does not claim real Hopper launch or a macOS host run.
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#1066
No description provided.