[PR #1817] [MERGED] fix(hopper): preserve startup failures and recover denied launches #4834

Closed
opened 2026-10-31 00:55:32 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/1817
Author: @morluto
Created: 10/11/2026
Status: ✅ Merged
Merged: 10/11/2026
Merged by: @N0zoM1z0

Base: main ← Head: codex/fix-hopper-startup-recovery


📝 Commits (2)

  • 729a8a8 fix(hopper): recover failed startup without losing ownership
  • 02d4c2e fix(hopper): authenticate cleanup and retain ambiguous handoffs

📊 Changes

25 files changed (+1074 additions, -166 deletions)

View changed files

📝 README.md (+2 -2)
📝 bridge/hopper_bridge.py (+89 -66)
📝 docs/installation.md (+19 -0)
📝 docs/mcp-contracts.md (+1 -0)
📝 src/config.test.ts (+35 -0)
📝 src/config/environment.ts (+11 -2)
📝 src/config/parseConfig.ts (+1 -0)
📝 src/config/providerStartupTimeout.ts (+5 -3)
📝 src/config/types.ts (+1 -0)
📝 src/ghidra/GhidraClient.ts (+1 -1)
📝 src/hopper/BridgeLauncher.ts (+13 -0)
📝 src/hopper/HopperClient.ts (+104 -35)
➕ src/hopper/HopperProcessDiagnostic.test.ts (+54 -0)
📝 src/hopper/HopperProcessDiagnostic.ts (+19 -0)
📝 src/hopper/HopperProvider.ts (+1 -0)
📝 src/hopper/HopperSessionValues.ts (+3 -0)
➕ tests/acceptance/providers/hopperStartupRecovery.test.ts (+304 -0)
📝 tests/boundary/cli/hopperLaunchFailureCli.test.ts (+14 -45)
➕ tests/boundary/providers/hopper/hopperBridgeReconnect.test.ts (+63 -0)
📝 tests/boundary/providers/hopper/hopperClient.cleanupRetry.test.ts (+13 -2)

...and 5 more files

📄 Description

Refs #1808.

After a native Hopper launch failed, REA retained an unconfirmed GUI handoff but discarded the startup result. Later tools returned unreadable_output from an unrelated "already started" error, and operational health lost the original cause. Preserve the typed startup failure and launcher diagnostics while cleanup is pending, including deadlines during the initial health handshake.

Release a denied launch only when the helper returns its complete Apple Events authorization rejection (exit 2, no signal, closed and untruncated output) and all native invocation arguments are literal-safe. The vendor helper interpolates arguments into AppleScript without escaping: a real Hopper 6.6 probe opened a document and then emitted the identical rejection. Quotes, backslashes, and line breaks therefore withhold the pre-handoff conclusion without changing caller inputs. Other ambiguous failures retain the runtime and target reservation. An established denial reports the macOS Automation remedy and permits retry.

Cleanup requires a successful token-authenticated health handshake with the exact run identity. Disconnect a rejected initial handshake before cleanup so it cannot authorize document shutdown. Explicit close can reconnect to a late bridge or a previously valid session whose transport failed. The shipped Python bridge now accepts successive connections, survives disconnected writers, and remains available after an unconfirmed shutdown. Its idle reconnect window is 30 seconds, matching the cleanup handshake budget; expiry releases Hopper's Python execution thread without claiming document closure.

Add REA_HOPPER_STARTUP_TIMEOUT_MS to shared CLI/MCP provider configuration, using the same validated integer range as Ghidra and retaining Hopper's 120-second default. Document timeout and recovery behavior. Replace the touched CLI test's global lease scan with recovery of its exact target through the production lease API.

Validation:

  • Focused config/Hopper/CLI/MCP coverage: 25 files, 184 passing tests, two platform skips. Includes same-server denial recovery, caller-controlled arguments, mismatched initial and late run/token handshakes, health deadline diagnostics, transport loss, lost replies, unconfirmed shutdown retry, and reconnect expiry.
  • Ghidra provider configuration regression: 40 tests passed in the original change.
  • npm run check, npm run docs:check, npm run verify:test-discovery, and the pre-push check passed; all 838 test files are discovered exactly once.
  • Real npm run verify:hopper passed on Hopper 6.6.0-demo after these changes: 200 successful calls, 63 expected rejections, confirmed cleanup, and survival of an unrelated process. The original change also passed on Hopper 6.1.0-demo.
  • Real Hopper 6.6 reconnect probe: interrupt a successfully authenticated session through a local socket relay, then explicitly close through the production client and native launcher. A fresh connection authenticated the same run, confirmed document closure, and removed the runtime.
  • Real native Hopper 6.6 diagnostic probe: an owned target opened before the helper emitted the exact authorization rejection. Through the compiled stdio MCP server, REA returned cleanup_incomplete, retained its exact runtime, preserved the failure on repeated calls, and refused to claim successful close. A separate authenticated session observed document creation, and explicit probe cleanup confirmed the exact executable path. This supersedes the earlier inference that matching diagnostic text alone established no handoff. The initial 6.1/6.6 fault probes establish diagnostic compatibility only; simulated denial recovery does not establish a real Claude Desktop TCC-denial reproduction.
  • Real CLI analyze /usr/libexec/sharingd --provider hopper --json with Hopper 6.6 and REA_HOPPER_STARTUP_TIMEOUT_MS=900000 exited successfully in 46.30 seconds, returning 41,920 procedures and 25,495 strings. Hopper returned to idle and no new Hopper crash report appeared. This host runs macOS 26.3 arm64, not the reporter's macOS 27.

The native helper does not expose a structured authorization error. Changed or localized diagnostics, incomplete output, signals, and other script failures retain conservative ownership. The reporter's macOS 27 Hopper SIGSEGV remains unverified; launcher exit 0 and missing bridge readiness do not establish a GUI crash.


🔄 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/1817 **Author:** [@morluto](https://github.com/morluto) **Created:** 10/11/2026 **Status:** ✅ Merged **Merged:** 10/11/2026 **Merged by:** [@N0zoM1z0](https://github.com/N0zoM1z0) **Base:** `main` ← **Head:** `codex/fix-hopper-startup-recovery` --- ### 📝 Commits (2) - [`729a8a8`](https://github.com/morluto/rea/commit/729a8a8510389ed7e6cb2075f1952943b92301fa) fix(hopper): recover failed startup without losing ownership - [`02d4c2e`](https://github.com/morluto/rea/commit/02d4c2edb6eeab2e91bc076b14605b9384222992) fix(hopper): authenticate cleanup and retain ambiguous handoffs ### 📊 Changes **25 files changed** (+1074 additions, -166 deletions) <details> <summary>View changed files</summary> 📝 `README.md` (+2 -2) 📝 `bridge/hopper_bridge.py` (+89 -66) 📝 `docs/installation.md` (+19 -0) 📝 `docs/mcp-contracts.md` (+1 -0) 📝 `src/config.test.ts` (+35 -0) 📝 `src/config/environment.ts` (+11 -2) 📝 `src/config/parseConfig.ts` (+1 -0) 📝 `src/config/providerStartupTimeout.ts` (+5 -3) 📝 `src/config/types.ts` (+1 -0) 📝 `src/ghidra/GhidraClient.ts` (+1 -1) 📝 `src/hopper/BridgeLauncher.ts` (+13 -0) 📝 `src/hopper/HopperClient.ts` (+104 -35) ➕ `src/hopper/HopperProcessDiagnostic.test.ts` (+54 -0) 📝 `src/hopper/HopperProcessDiagnostic.ts` (+19 -0) 📝 `src/hopper/HopperProvider.ts` (+1 -0) 📝 `src/hopper/HopperSessionValues.ts` (+3 -0) ➕ `tests/acceptance/providers/hopperStartupRecovery.test.ts` (+304 -0) 📝 `tests/boundary/cli/hopperLaunchFailureCli.test.ts` (+14 -45) ➕ `tests/boundary/providers/hopper/hopperBridgeReconnect.test.ts` (+63 -0) 📝 `tests/boundary/providers/hopper/hopperClient.cleanupRetry.test.ts` (+13 -2) _...and 5 more files_ </details> ### 📄 Description Refs #1808. After a native Hopper launch failed, REA retained an unconfirmed GUI handoff but discarded the startup result. Later tools returned `unreadable_output` from an unrelated "already started" error, and operational health lost the original cause. Preserve the typed startup failure and launcher diagnostics while cleanup is pending, including deadlines during the initial health handshake. Release a denied launch only when the helper returns its complete Apple Events authorization rejection (exit 2, no signal, closed and untruncated output) and all native invocation arguments are literal-safe. The vendor helper interpolates arguments into AppleScript without escaping: a real Hopper 6.6 probe opened a document and then emitted the identical rejection. Quotes, backslashes, and line breaks therefore withhold the pre-handoff conclusion without changing caller inputs. Other ambiguous failures retain the runtime and target reservation. An established denial reports the macOS Automation remedy and permits retry. Cleanup requires a successful token-authenticated health handshake with the exact run identity. Disconnect a rejected initial handshake before cleanup so it cannot authorize document shutdown. Explicit close can reconnect to a late bridge or a previously valid session whose transport failed. The shipped Python bridge now accepts successive connections, survives disconnected writers, and remains available after an unconfirmed shutdown. Its idle reconnect window is 30 seconds, matching the cleanup handshake budget; expiry releases Hopper's Python execution thread without claiming document closure. Add `REA_HOPPER_STARTUP_TIMEOUT_MS` to shared CLI/MCP provider configuration, using the same validated integer range as Ghidra and retaining Hopper's 120-second default. Document timeout and recovery behavior. Replace the touched CLI test's global lease scan with recovery of its exact target through the production lease API. Validation: - Focused config/Hopper/CLI/MCP coverage: 25 files, 184 passing tests, two platform skips. Includes same-server denial recovery, caller-controlled arguments, mismatched initial and late run/token handshakes, health deadline diagnostics, transport loss, lost replies, unconfirmed shutdown retry, and reconnect expiry. - Ghidra provider configuration regression: 40 tests passed in the original change. - `npm run check`, `npm run docs:check`, `npm run verify:test-discovery`, and the pre-push check passed; all 838 test files are discovered exactly once. - Real `npm run verify:hopper` passed on Hopper 6.6.0-demo after these changes: 200 successful calls, 63 expected rejections, confirmed cleanup, and survival of an unrelated process. The original change also passed on Hopper 6.1.0-demo. - Real Hopper 6.6 reconnect probe: interrupt a successfully authenticated session through a local socket relay, then explicitly close through the production client and native launcher. A fresh connection authenticated the same run, confirmed document closure, and removed the runtime. - Real native Hopper 6.6 diagnostic probe: an owned target opened before the helper emitted the exact authorization rejection. Through the compiled stdio MCP server, REA returned `cleanup_incomplete`, retained its exact runtime, preserved the failure on repeated calls, and refused to claim successful close. A separate authenticated session observed document creation, and explicit probe cleanup confirmed the exact executable path. This supersedes the earlier inference that matching diagnostic text alone established no handoff. The initial 6.1/6.6 fault probes establish diagnostic compatibility only; simulated denial recovery does not establish a real Claude Desktop TCC-denial reproduction. - Real CLI `analyze /usr/libexec/sharingd --provider hopper --json` with Hopper 6.6 and `REA_HOPPER_STARTUP_TIMEOUT_MS=900000` exited successfully in 46.30 seconds, returning 41,920 procedures and 25,495 strings. Hopper returned to idle and no new Hopper crash report appeared. This host runs macOS 26.3 arm64, not the reporter's macOS 27. The native helper does not expose a structured authorization error. Changed or localized diagnostics, incomplete output, signals, and other script failures retain conservative ownership. The reporter's macOS 27 Hopper SIGSEGV remains unverified; launcher exit 0 and missing bridge readiness do not establish a GUI crash. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-31 00:55:32 +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#4834
No description provided.