[GH-ISSUE #7637] [Bug] macOS helper compilation failure prevents all target-free MCP availability discovery #4563

Closed
opened 2026-10-31 00:50:48 +00:00 by bsakaguchi · 0 comments
Owner

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

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 arm64, starting the MCP server with DEVELOPER_DIR pointing to a nonexistent directory lets initialization succeed, but this target-free request fails:

{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"binary_session","arguments":{}}}

The response has isError: true, no structuredContent, and no availability inventory. Its text contains execution_failure, retryable: false, and an ownership-preparation compiler error. No analysis target or native operation was requested.

Under the same broken environment, rea capabilities --json exits 0 and returns its structured capability status. rea doctor --json returns its diagnostic document with exit 1 for missing optional engines and the isolated home's missing skill identity; it does not identify the invalid developer directory. Removing only DEVELOPER_DIR in a fresh isolated control restores binary_session {} and its 16 availability groups. Apktool is absent in that control and is ordinarily reported unavailable.

Reproduction

  1. Use the built baseline checkout, with its locked dependencies and Node 24.22.1 on PATH. From the checkout root, run the following standard-library Python client. It creates isolated HOME/XDG/TMPDIR paths, drives raw newline-delimited JSON-RPC, runs two broken environments and one control, closes each server, and removes its temporary files. No fixture binary is needed.
work_dir="$(mktemp -d)"
python3 - "$PWD" "$work_dir" <<'PYTHON'
import json, os, pathlib, queue, subprocess, sys, tempfile, threading
root = pathlib.Path(sys.argv[1]).resolve()
for label, broken in [('broken-1', True), ('broken-2', True), ('control', False)]:
 with tempfile.TemporaryDirectory(prefix='rea-verify-', dir=sys.argv[2]) as directory:
  work = pathlib.Path(directory)
  env = {'PATH': os.environ['PATH'], 'NO_COLOR': '1', 'REA_LOG_LEVEL': 'silent'}
  for key, name in [('HOME','home'), ('XDG_CONFIG_HOME','config'),
                    ('XDG_DATA_HOME','data'), ('XDG_CACHE_HOME','cache'),
                    ('XDG_STATE_HOME','state'), ('TMPDIR','tmp')]:
   (work/name).mkdir(); env[key] = str(work/name)
  if broken: env['DEVELOPER_DIR'] = str(work/'nonexistent-developer')
  command = ['node', str(root/'scripts/rea.mjs')]
  if label == 'broken-1':
   for args in [['capabilities','--json'], ['doctor','--json']]:
    result = subprocess.run(command+args, cwd=work, env=env,
                            capture_output=True, text=True, timeout=120)
    body = json.loads(result.stdout)
    print(label, args, 'exit', result.returncode,
          'healthy', body.get('healthy'),
          'failed_checks', [c['name'] for c in body.get('checks',[]) if not c['ok']])
  with (work/'stderr.log').open('w') as log:
   process = subprocess.Popen(command+['mcp'], cwd=work, env=env,
       stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=log, text=True)
   lines = queue.Queue()
   def read_lines():
    for line in process.stdout: lines.put(line)
    lines.put(None)
   threading.Thread(target=read_lines, daemon=True).start()
   def send(message):
    process.stdin.write(json.dumps(message)+'\n'); process.stdin.flush()
   def request(identifier, method, params):
    send({'jsonrpc':'2.0','id':identifier,'method':method,'params':params})
    while True:
     line = lines.get(timeout=150)
     if line is None: raise RuntimeError('Server closed stdout')
     message = json.loads(line)
     if message.get('id') == identifier: return message
   try:
    request(1, 'initialize', {'protocolVersion':'2025-06-18','capabilities':{},
            'clientInfo':{'name':'independent-verifier','version':'1'}})
    send({'jsonrpc':'2.0','method':'notifications/initialized'})
    result = request(2, 'tools/call', {'name':'binary_session','arguments':{}})['result']
    if result.get('isError'):
     body = json.loads(next(c['text'] for c in result['content'] if c['type']=='text'))
     print(label, json.dumps(body).replace(str(work), '<isolated-work>'))
    else:
     body = result['structuredContent']['result']
     print(label, 'open', body['open'], 'availability_groups',
           len(body['tool_availability']['groups']))
   finally:
    process.stdin.close()
    try: process.wait(timeout=25)
    except subprocess.TimeoutExpired: process.terminate(); process.wait(timeout=10)
    print(label, 'server_exit', process.returncode)
PYTHON
rmdir "$work_dir"
  1. Both broken-* calls return the error below; control prints open False availability_groups 16. All servers exit 0.

The initial independent verification reproduced the failure in 2 of 2 attempts with one successful control. The self-contained client above was then run as a separate verification of these reproduction instructions: both broken cases failed again and its control succeeded. Total: 4 of 4 broken cases failed and 2 of 2 controls succeeded; all six servers exited 0.

Expected behavior

The installation workflow directs callers to request binary_session {} before choosing an operation and describes availability, reasons and client requirements before a target is opened. The MCP discovery contract describes a complete inventory for the current host and provider state.

Swift is a prerequisite for macOS process capture and owned provider-process supervision. A broken compiler should prevent those operations from being advertised as ready, while discovery still returns the other operation groups. This report does not ask REA to run owned provider processes without ownership verification or to install developer tools.

Impact and scope

P2: on the tested macOS host, a bad developer-directory setting suppresses the entire documented MCP availability result, including unrelated operations. The failure occurs before any target is opened. It is repeatable in fresh isolated configurations, and removing that setting restored discovery in the control.

Observed scope is a nonexistent DEVELOPER_DIR on macOS arm64. A host with genuinely absent Command Line Tools, other compiler failures, Linux/Windows behavior, and downstream agent decisions were not tested. There was no observed data loss or security boundary crossing. The control establishes an environment workaround for this reproduction; it does not establish recovery for a genuinely missing toolchain.

REA version

Exact node scripts/rea.mjs --version output: 6.8.0.

Source baseline: 65f1c447344a423fa2785847dbcea1e9a9e2454a.

Runtime and platform

Node.js v24.22.1; macOS 27.0.1, build 26A434; arm64. Production stdio MCP server with isolated HOME/XDG/TMPDIR and no inherited REA provider configuration. The normal host has a working developer toolchain; the failing cases override its selection with a nonexistent directory.

Target and artifact context

No target, APK, Mach-O, debugger session or GUI app was opened. The only tools/call request was binary_session with empty arguments.

Interface

MCP

Earliest failure stage

Setup or provider admission

Relevant output or evidence

Selected fields from the independently observed failure, with the temporary work path replaced by <isolated-work>:

{
  "isError": true,
  "error": {
    "code": "execution_failure",
    "category": "execution_failure",
    "retryable": false,
    "details": {
      "provider_id": "apktool",
      "operation": "inspect_apktool_client",
      "diagnostics": {
        "stage": "ownership_preparation",
        "compiler_outcome": {
          "outcome": "exited",
          "code": 1,
          "signal": null,
          "stderr": "xcrun: error: missing DEVELOPER_DIR path: <isolated-work>/nonexistent-developer\n"
        }
      }
    }
  }
}

This is an excerpt of the MCP result and its parsed text error, not a literal wire envelope. The full error also carries the compiler's captured output and generic remediation to run rea doctor. The client above prints that text error directly.

Observed healthy control: open: false, 16 availability groups, including:

{"names":["decode_android_resources","inspect_apktool_client"],"available":false,"reason":"provider_unavailable","remediation":"The selected apktool launcher was not found: apktool"}

Observed CLI comparison in the broken environment: capabilities exits 0; doctor exits 1. Doctor's failed check names are ghidra, ida-registration, jadx, kuna-installation, and skill:identity. Its node and host checks pass. CLI capabilities and MCP tool availability are distinct projections; the CLI comparison shows diagnostic commands remain callable, not that they return the same inventory.

Contract locators at the baseline: docs/installation.md:366-378 and docs/mcp-contracts.md:614-623 describe target-free discovery. docs/process-capture.md:38-44 scopes Swift to process ownership/capture and owned provider supervision. The installation guide does not list a working Swift/Xcode toolchain as a general prerequisite for every REA operation or discovery.

Typed outcome or recovery expected

Return successful session status with the affected operations marked unavailable and an actionable compiler/toolchain reason. Preserve the availability of unrelated operations and retain execution-time ownership checks. A code fix and its restoration have not been tested.

Analysis or suspected cause

Source-derived explanation consistent with the runtime failure: src/server/registerSessionStatusTool.ts:115-127 converts an availability probe's AnalysisError into a whole-call error before building the inventory. src/apktool/ApktoolProvider.ts:326-356 rethrows errors that are neither configuration failures nor recognized ownership prerequisites. src/apktool/ApktoolConfiguration.ts:475-512 wraps Darwin preparation failures as ProviderAdapterError with compiler diagnostics. src/application/ownedProcessAvailability.ts:8-42 recognizes the Linux prerequisite class only.

The Apktool labels identify the availability probe that encountered the shared prerequisite, and its diagnostics preserve the real xcrun failure. This report concerns the loss of the complete discovery result, not a separate claim that those provenance labels are incorrect.

Additional context

Related, with different closure criteria:

  • #4311 requests native macOS permission and Xcode preflight checks in doctor/capability entries. It does not report this target-free MCP hard failure. Adding its requested checks could leave the failing probe unchanged.
  • #7419 concerns Linux-only debugger starts advertised available on macOS in a successfully returned inventory.
  • Closed #7164 and merged #7180 fixed the analogous missing-Linux-ps discovery failure. The PR explicitly says Darwin helper-compilation failures remain unchanged. That fix is an ancestor of this baseline; this report is the uncovered macOS case, not a regression claim for its Linux fix.
  • Closed #1993 and merged #2506 address compiler diagnostics on failed operations, rather than keeping availability discovery usable. Compiler exit/stderr are preserved in this reproduction.
Originally created by @coygeek on GitHub (Oct 28, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/7637 ### 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 arm64, starting the MCP server with `DEVELOPER_DIR` pointing to a nonexistent directory lets initialization succeed, but this target-free request fails: ```json {"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"binary_session","arguments":{}}} ``` The response has `isError: true`, no `structuredContent`, and no availability inventory. Its text contains `execution_failure`, `retryable: false`, and an ownership-preparation compiler error. No analysis target or native operation was requested. Under the same broken environment, `rea capabilities --json` exits 0 and returns its structured capability status. `rea doctor --json` returns its diagnostic document with exit 1 for missing optional engines and the isolated home's missing skill identity; it does not identify the invalid developer directory. Removing only `DEVELOPER_DIR` in a fresh isolated control restores `binary_session {}` and its 16 availability groups. Apktool is absent in that control and is ordinarily reported unavailable. ### Reproduction 1. Use the built baseline checkout, with its locked dependencies and Node 24.22.1 on `PATH`. From the checkout root, run the following standard-library Python client. It creates isolated HOME/XDG/TMPDIR paths, drives raw newline-delimited JSON-RPC, runs two broken environments and one control, closes each server, and removes its temporary files. No fixture binary is needed. ```sh work_dir="$(mktemp -d)" python3 - "$PWD" "$work_dir" <<'PYTHON' import json, os, pathlib, queue, subprocess, sys, tempfile, threading root = pathlib.Path(sys.argv[1]).resolve() for label, broken in [('broken-1', True), ('broken-2', True), ('control', False)]: with tempfile.TemporaryDirectory(prefix='rea-verify-', dir=sys.argv[2]) as directory: work = pathlib.Path(directory) env = {'PATH': os.environ['PATH'], 'NO_COLOR': '1', 'REA_LOG_LEVEL': 'silent'} for key, name in [('HOME','home'), ('XDG_CONFIG_HOME','config'), ('XDG_DATA_HOME','data'), ('XDG_CACHE_HOME','cache'), ('XDG_STATE_HOME','state'), ('TMPDIR','tmp')]: (work/name).mkdir(); env[key] = str(work/name) if broken: env['DEVELOPER_DIR'] = str(work/'nonexistent-developer') command = ['node', str(root/'scripts/rea.mjs')] if label == 'broken-1': for args in [['capabilities','--json'], ['doctor','--json']]: result = subprocess.run(command+args, cwd=work, env=env, capture_output=True, text=True, timeout=120) body = json.loads(result.stdout) print(label, args, 'exit', result.returncode, 'healthy', body.get('healthy'), 'failed_checks', [c['name'] for c in body.get('checks',[]) if not c['ok']]) with (work/'stderr.log').open('w') as log: process = subprocess.Popen(command+['mcp'], cwd=work, env=env, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=log, text=True) lines = queue.Queue() def read_lines(): for line in process.stdout: lines.put(line) lines.put(None) threading.Thread(target=read_lines, daemon=True).start() def send(message): process.stdin.write(json.dumps(message)+'\n'); process.stdin.flush() def request(identifier, method, params): send({'jsonrpc':'2.0','id':identifier,'method':method,'params':params}) while True: line = lines.get(timeout=150) if line is None: raise RuntimeError('Server closed stdout') message = json.loads(line) if message.get('id') == identifier: return message try: request(1, 'initialize', {'protocolVersion':'2025-06-18','capabilities':{}, 'clientInfo':{'name':'independent-verifier','version':'1'}}) send({'jsonrpc':'2.0','method':'notifications/initialized'}) result = request(2, 'tools/call', {'name':'binary_session','arguments':{}})['result'] if result.get('isError'): body = json.loads(next(c['text'] for c in result['content'] if c['type']=='text')) print(label, json.dumps(body).replace(str(work), '<isolated-work>')) else: body = result['structuredContent']['result'] print(label, 'open', body['open'], 'availability_groups', len(body['tool_availability']['groups'])) finally: process.stdin.close() try: process.wait(timeout=25) except subprocess.TimeoutExpired: process.terminate(); process.wait(timeout=10) print(label, 'server_exit', process.returncode) PYTHON rmdir "$work_dir" ``` 2. Both `broken-*` calls return the error below; `control` prints `open False availability_groups 16`. All servers exit 0. The initial independent verification reproduced the failure in 2 of 2 attempts with one successful control. The self-contained client above was then run as a separate verification of these reproduction instructions: both broken cases failed again and its control succeeded. Total: 4 of 4 broken cases failed and 2 of 2 controls succeeded; all six servers exited 0. ### Expected behavior The installation workflow directs callers to request `binary_session {}` before choosing an operation and describes availability, reasons and client requirements before a target is opened. The MCP discovery contract describes a complete inventory for the current host and provider state. Swift is a prerequisite for macOS process capture and owned provider-process supervision. A broken compiler should prevent those operations from being advertised as ready, while discovery still returns the other operation groups. This report does not ask REA to run owned provider processes without ownership verification or to install developer tools. ### Impact and scope P2: on the tested macOS host, a bad developer-directory setting suppresses the entire documented MCP availability result, including unrelated operations. The failure occurs before any target is opened. It is repeatable in fresh isolated configurations, and removing that setting restored discovery in the control. Observed scope is a nonexistent `DEVELOPER_DIR` on macOS arm64. A host with genuinely absent Command Line Tools, other compiler failures, Linux/Windows behavior, and downstream agent decisions were not tested. There was no observed data loss or security boundary crossing. The control establishes an environment workaround for this reproduction; it does not establish recovery for a genuinely missing toolchain. ### REA version Exact `node scripts/rea.mjs --version` output: `6.8.0`. Source baseline: `65f1c447344a423fa2785847dbcea1e9a9e2454a`. ### Runtime and platform Node.js v24.22.1; macOS 27.0.1, build 26A434; arm64. Production stdio MCP server with isolated HOME/XDG/TMPDIR and no inherited REA provider configuration. The normal host has a working developer toolchain; the failing cases override its selection with a nonexistent directory. ### Target and artifact context No target, APK, Mach-O, debugger session or GUI app was opened. The only tools/call request was `binary_session` with empty arguments. ### Interface MCP ### Earliest failure stage Setup or provider admission ### Relevant output or evidence Selected fields from the independently observed failure, with the temporary work path replaced by `<isolated-work>`: ```json { "isError": true, "error": { "code": "execution_failure", "category": "execution_failure", "retryable": false, "details": { "provider_id": "apktool", "operation": "inspect_apktool_client", "diagnostics": { "stage": "ownership_preparation", "compiler_outcome": { "outcome": "exited", "code": 1, "signal": null, "stderr": "xcrun: error: missing DEVELOPER_DIR path: <isolated-work>/nonexistent-developer\n" } } } } } ``` This is an excerpt of the MCP result and its parsed text error, not a literal wire envelope. The full error also carries the compiler's captured output and generic remediation to run `rea doctor`. The client above prints that text error directly. Observed healthy control: `open: false`, 16 availability groups, including: ```json {"names":["decode_android_resources","inspect_apktool_client"],"available":false,"reason":"provider_unavailable","remediation":"The selected apktool launcher was not found: apktool"} ``` Observed CLI comparison in the broken environment: capabilities exits 0; doctor exits 1. Doctor's failed check names are `ghidra`, `ida-registration`, `jadx`, `kuna-installation`, and `skill:identity`. Its node and host checks pass. CLI capabilities and MCP tool availability are distinct projections; the CLI comparison shows diagnostic commands remain callable, not that they return the same inventory. Contract locators at the baseline: `docs/installation.md:366-378` and `docs/mcp-contracts.md:614-623` describe target-free discovery. `docs/process-capture.md:38-44` scopes Swift to process ownership/capture and owned provider supervision. The installation guide does not list a working Swift/Xcode toolchain as a general prerequisite for every REA operation or discovery. ### Typed outcome or recovery expected Return successful session status with the affected operations marked unavailable and an actionable compiler/toolchain reason. Preserve the availability of unrelated operations and retain execution-time ownership checks. A code fix and its restoration have not been tested. ### Analysis or suspected cause Source-derived explanation consistent with the runtime failure: `src/server/registerSessionStatusTool.ts:115-127` converts an availability probe's `AnalysisError` into a whole-call error before building the inventory. `src/apktool/ApktoolProvider.ts:326-356` rethrows errors that are neither configuration failures nor recognized ownership prerequisites. `src/apktool/ApktoolConfiguration.ts:475-512` wraps Darwin preparation failures as `ProviderAdapterError` with compiler diagnostics. `src/application/ownedProcessAvailability.ts:8-42` recognizes the Linux prerequisite class only. The Apktool labels identify the availability probe that encountered the shared prerequisite, and its diagnostics preserve the real `xcrun` failure. This report concerns the loss of the complete discovery result, not a separate claim that those provenance labels are incorrect. ### Additional context Related, with different closure criteria: - [#4311](https://github.com/morluto/rea/issues/4311) requests native macOS permission and Xcode preflight checks in doctor/capability entries. It does not report this target-free MCP hard failure. Adding its requested checks could leave the failing probe unchanged. - [#7419](https://github.com/morluto/rea/issues/7419) concerns Linux-only debugger starts advertised available on macOS in a successfully returned inventory. - Closed [#7164](https://github.com/morluto/rea/issues/7164) and merged [#7180](https://github.com/morluto/rea/pull/7180) fixed the analogous missing-Linux-ps discovery failure. The PR explicitly says Darwin helper-compilation failures remain unchanged. That fix is an ancestor of this baseline; this report is the uncovered macOS case, not a regression claim for its Linux fix. - Closed [#1993](https://github.com/morluto/rea/issues/1993) and merged [#2506](https://github.com/morluto/rea/pull/2506) address compiler diagnostics on failed operations, rather than keeping availability discovery usable. Compiler exit/stderr are preserved in this reproduction.
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#4563
No description provided.