[PR #3011] [CLOSED] fix(hopper): release a Spotlight not-found launch from the signed helper #5557

Closed
opened 2026-10-31 01:07:34 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/3011
Author: @bruno-candia
Created: 10/13/2026
Status: ❌ Closed

Base: main ← Head: fix/hopper-verified-spotlight-recovery


📝 Commits (7)

  • 044c0b7 fix(hopper): recover from a Spotlight not-found launch
  • e073417 fix(hopper): require empty stdout for native launch signatures
  • 03f907d fix(hopper): retain ownership after unverified Spotlight reports
  • c65828b test(hopper): run the late-bridge acceptance rows with their fixtures
  • 2bd3124 fix(hopper): release a Spotlight not-found launch from the signed helper
  • 2ac3452 Merge origin/main into fix/hopper-verified-spotlight-recovery
  • e757dfc Merge remote-tracking branch 'origin/main' into HEAD

📊 Changes

1 file changed (+66 additions, -0 deletions)

View changed files

📝 tests/boundary/providers/hopper/bridgeLauncher.test.ts (+66 -0)

📄 Description

Summary

Builds on #2795 (its three commits are included here until it merges; review only the last two). #2795 now reports the Spotlight not-found failure with setup guidance but, after review, deliberately keeps ownership unknown: a custom launcher could forward the same report after a handoff. As a result the original bug remains — after a Spotlight not-found launch, every later open_binary in the session fails with cleanup_incomplete until the MCP server restarts, even once indexing is enabled.

This adds the provenance gate the review asked for (Greptile P1, Codex P2 on #2795): the not-found report confirms that no document exists only when the executed launcher is Hopper's own vendor-signed helper.

Refs #1808, #2795.

Problem and expected behavior

  • Trigger: macOS with Spotlight indexing off for Hopper's volume, open_binary through the native hopper helper.
  • Violated invariant: a launch that provably failed before handoff must leave the session reusable.
  • New behavior: before running the helper, HopperApplicationLauncher verifies the exact launcher file with codesign --verify --strict -R'= identifier "hopper" and anchor apple generic and certificate leaf[subject.OU] = "2AMA2753NF"'. Only a verified helper sets applicationNotFoundBeforeHandoff on the launch, and only then does HopperClient mark shutdown confirmed for the complete not-found report (exit 3, exact stderr, empty stdout). Wrappers, custom launchers, extra output and non-macOS hosts keep the conservative unknown-handoff outcome from #2795.

Change and scope

  • src/hopper/BridgeLauncher.ts: applicationNotFoundBeforeHandoff launch metadata; injectable verifyNativeHelper dependency; default codesign verification reusing HOPPER_PUBLISHER.teamIdentifier. Verified only on macOS, for the non-owning native launch, before the helper runs.
  • src/hopper/HopperClient.ts: confirms shutdown for the not-found report only with that metadata.
  • tests/acceptance/providers/hopperStartupRecovery.test.ts (separate commit): the late-bridge rows used test.each, which does not pass the workspace fixture, so the file failed typecheck (Property 'workspace' does not exist) and the rows never ran. test.for runs them; the not-found row now expects provider_operation_health.state: "unknown", which is what a non-zero launcher exit produces and what the boundary tests already assert.
  • Not included: locating Hopper without Spotlight.

Ordinary fix within existing boundaries; maintainer scope decision: not applicable.

Contract and boundary impact

  • Semantic owner and earliest changed stage: Hopper launch metadata (src/hopper/BridgeLauncher.ts).

  • Contribution scope fit and linked maintainer decision: bug fix; follows the review on #2795.

  • CLI and MCP/tool-catalog contract: none; error text unchanged from #2795.

  • Provider, bridge, target-format, or platform compatibility: macOS native launch only; adds one codesign invocation per launch.

  • Evidence, artifact, provenance, or reconstruction contract: none.

  • Target selection, authority, upstream behavior, and cleanup details: cleanup completes only for the verified helper; every other launcher keeps the runtime and lease.

  • Generated metadata, package, or installation impact: none.

  • Launch or attach to a process.

Evidence and regression coverage

  • Tests added: boundary tests in hopperClient.helperFailure.test.ts (verified helper releases ownership and the same client starts again twice; unverified launcher and verified helper with extra stdout both stay cleanup_incomplete) and in bridgeLauncher.test.ts (flag only with a verified helper; no verification outside macOS; the default verifier rejects a non-Hopper executable).

  • Base reproduction: with HopperClient.ts from #2795's head, releases a verified native helper's Spotlight not-found launch so the session can retry fails; with this change it passes.

  • Real signature: on macOS with Hopper Disassembler 6.6.0-demo, Contents/MacOS/hopper satisfies the requirement (Identifier=hopper, TeamIdentifier=2AMA2753NF); the main Hopper Disassembler executable does not, so the identifier pins the helper.

  • Remaining proof gaps: not reproduced end to end with Spotlight disabled on a real host (that needs changing the system indexing setting). The requirement was checked against the 6.6.0-demo build only.

  • Unsupported, incomplete, unavailable, or uncertain outcomes remain visible.

Validation performed

  • npm run check:fast — typecheck and lint passed (on #2795's head it fails with the test.each typecheck error above)
  • npm run test:focused -- all src/hopper/*.test.ts, tests/boundary/providers/hopper/*.test.ts and tests/acceptance/providers/hopperStartupRecovery.test.ts — 30 files, 1 failure: retains a denial-shaped failure when caller arguments can alter the helper's AppleScript (ENOENT on loader-target in acquireHopperTargetLease), which fails the same way on #2795's head and on main on this host
  • codesign --verify --strict -R'<requirement>' against the installed helper — satisfied

Compatibility, safety, and release

  • Breaking changes or migration steps: none.
  • Real Hopper/Ghidra, browser, or OS coverage: macOS, Hopper Disassembler 6.6.0-demo (signature check only).
  • Package or release metadata impact: none.
  • Security, privacy, process, or containment review: verification runs before the helper executes; a file swapped between verification and execution would need write access to the Hopper bundle. On failure the outcome stays conservative.

Before merge

  • This PR addresses a concrete problem or an agreed enhancement.
  • Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff.
  • The PR has one focused outcome and the title follows type(scope): outcome.
  • Related issue is linked, or the reason for not linking one is stated above.
  • Tests cover changed observable behavior and meaningful failure paths.
  • Owning docs, contracts, and generated metadata are updated where needed.
  • User-visible CLI/MCP changes include representative output. (No new output; message unchanged from #2795.)
  • I checked the final diff for secrets, unrelated cleanup, and unsupported claims.

🤖 Generated with Claude Code


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/morluto/rea/pull/3011 **Author:** [@bruno-candia](https://github.com/bruno-candia) **Created:** 10/13/2026 **Status:** ❌ Closed **Base:** `main` ← **Head:** `fix/hopper-verified-spotlight-recovery` --- ### 📝 Commits (7) - [`044c0b7`](https://github.com/morluto/rea/commit/044c0b72256c61c2ffea4b0af36d94ed95e08c55) fix(hopper): recover from a Spotlight not-found launch - [`e073417`](https://github.com/morluto/rea/commit/e0734170091cfaa22dcdf29adf7dcf6f2fc5195d) fix(hopper): require empty stdout for native launch signatures - [`03f907d`](https://github.com/morluto/rea/commit/03f907df680042789c1956996cf7845ff850776a) fix(hopper): retain ownership after unverified Spotlight reports - [`c65828b`](https://github.com/morluto/rea/commit/c65828b260b1ef5990e5af34e0caa28de18bdadc) test(hopper): run the late-bridge acceptance rows with their fixtures - [`2bd3124`](https://github.com/morluto/rea/commit/2bd31248335646afbd9a0a9ae1361aad3a557697) fix(hopper): release a Spotlight not-found launch from the signed helper - [`2ac3452`](https://github.com/morluto/rea/commit/2ac34529f102f1b77aa71a3e7e228ad919dbbc4d) Merge origin/main into fix/hopper-verified-spotlight-recovery - [`e757dfc`](https://github.com/morluto/rea/commit/e757dfc37d165b602b17f0441e3f3e55a7049332) Merge remote-tracking branch 'origin/main' into HEAD ### 📊 Changes **1 file changed** (+66 additions, -0 deletions) <details> <summary>View changed files</summary> 📝 `tests/boundary/providers/hopper/bridgeLauncher.test.ts` (+66 -0) </details> ### 📄 Description ## Summary Builds on #2795 (its three commits are included here until it merges; review only the last two). #2795 now reports the Spotlight not-found failure with setup guidance but, after review, deliberately keeps ownership unknown: a custom launcher could forward the same report after a handoff. As a result the original bug remains — after a Spotlight not-found launch, every later `open_binary` in the session fails with `cleanup_incomplete` until the MCP server restarts, even once indexing is enabled. This adds the provenance gate the review asked for (Greptile P1, Codex P2 on #2795): the not-found report confirms that no document exists only when the executed launcher is Hopper's own vendor-signed helper. Refs #1808, #2795. ## Problem and expected behavior - Trigger: macOS with Spotlight indexing off for Hopper's volume, `open_binary` through the native `hopper` helper. - Violated invariant: a launch that provably failed before handoff must leave the session reusable. - New behavior: before running the helper, `HopperApplicationLauncher` verifies the exact launcher file with `codesign --verify --strict -R'= identifier "hopper" and anchor apple generic and certificate leaf[subject.OU] = "2AMA2753NF"'`. Only a verified helper sets `applicationNotFoundBeforeHandoff` on the launch, and only then does `HopperClient` mark shutdown confirmed for the complete not-found report (exit 3, exact stderr, empty stdout). Wrappers, custom launchers, extra output and non-macOS hosts keep the conservative unknown-handoff outcome from #2795. ## Change and scope - `src/hopper/BridgeLauncher.ts`: `applicationNotFoundBeforeHandoff` launch metadata; injectable `verifyNativeHelper` dependency; default `codesign` verification reusing `HOPPER_PUBLISHER.teamIdentifier`. Verified only on macOS, for the non-owning native launch, before the helper runs. - `src/hopper/HopperClient.ts`: confirms shutdown for the not-found report only with that metadata. - `tests/acceptance/providers/hopperStartupRecovery.test.ts` (separate commit): the late-bridge rows used `test.each`, which does not pass the workspace fixture, so the file failed `typecheck` (`Property 'workspace' does not exist`) and the rows never ran. `test.for` runs them; the not-found row now expects `provider_operation_health.state: "unknown"`, which is what a non-zero launcher exit produces and what the boundary tests already assert. - Not included: locating Hopper without Spotlight. Ordinary fix within existing boundaries; maintainer scope decision: not applicable. ## Contract and boundary impact - Semantic owner and earliest changed stage: Hopper launch metadata (`src/hopper/BridgeLauncher.ts`). - Contribution scope fit and linked maintainer decision: bug fix; follows the review on #2795. - CLI and MCP/tool-catalog contract: none; error text unchanged from #2795. - Provider, bridge, target-format, or platform compatibility: macOS native launch only; adds one `codesign` invocation per launch. - Evidence, artifact, provenance, or reconstruction contract: none. - Target selection, authority, upstream behavior, and cleanup details: cleanup completes only for the verified helper; every other launcher keeps the runtime and lease. - Generated metadata, package, or installation impact: none. - [x] Launch or attach to a process. ## Evidence and regression coverage - Tests added: boundary tests in `hopperClient.helperFailure.test.ts` (verified helper releases ownership and the same client starts again twice; unverified launcher and verified helper with extra stdout both stay `cleanup_incomplete`) and in `bridgeLauncher.test.ts` (flag only with a verified helper; no verification outside macOS; the default verifier rejects a non-Hopper executable). - Base reproduction: with `HopperClient.ts` from #2795's head, `releases a verified native helper's Spotlight not-found launch so the session can retry` fails; with this change it passes. - Real signature: on macOS with Hopper Disassembler 6.6.0-demo, `Contents/MacOS/hopper` satisfies the requirement (`Identifier=hopper`, `TeamIdentifier=2AMA2753NF`); the main `Hopper Disassembler` executable does not, so the identifier pins the helper. - Remaining proof gaps: not reproduced end to end with Spotlight disabled on a real host (that needs changing the system indexing setting). The requirement was checked against the 6.6.0-demo build only. - [x] Unsupported, incomplete, unavailable, or uncertain outcomes remain visible. ## Validation performed - `npm run check:fast` — typecheck and lint passed (on #2795's head it fails with the `test.each` typecheck error above) - `npm run test:focused --` all `src/hopper/*.test.ts`, `tests/boundary/providers/hopper/*.test.ts` and `tests/acceptance/providers/hopperStartupRecovery.test.ts` — 30 files, 1 failure: `retains a denial-shaped failure when caller arguments can alter the helper's AppleScript` (ENOENT on `loader-target` in `acquireHopperTargetLease`), which fails the same way on #2795's head and on `main` on this host - `codesign --verify --strict -R'<requirement>'` against the installed helper — satisfied ## Compatibility, safety, and release - Breaking changes or migration steps: none. - Real Hopper/Ghidra, browser, or OS coverage: macOS, Hopper Disassembler 6.6.0-demo (signature check only). - Package or release metadata impact: none. - Security, privacy, process, or containment review: verification runs before the helper executes; a file swapped between verification and execution would need write access to the Hopper bundle. On failure the outcome stays conservative. ## Before merge - [x] This PR addresses a concrete problem or an agreed enhancement. - [x] Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff. - [x] The PR has one focused outcome and the title follows `type(scope): outcome`. - [x] Related issue is linked, or the reason for not linking one is stated above. - [x] Tests cover changed observable behavior and meaningful failure paths. - [x] Owning docs, contracts, and generated metadata are updated where needed. - [ ] User-visible CLI/MCP changes include representative output. (No new output; message unchanged from #2795.) - [x] I checked the final diff for secrets, unrelated cleanup, and unsupported claims. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-31 01:07:34 +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#5557
No description provided.