[GH-ISSUE #7397] [Bug] binary_session misclassifies unsupported navigation targets as missing providers #4404

Open
opened 2026-10-31 00:49:32 +00:00 by bsakaguchi · 0 comments
Owner

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

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

After open_binary successfully opens a generated XML plist as an artifact, binary_session {} says get_navigation_context is unavailable because of provider_missing and recommends configuring provider operations. On the same connection, get_navigation_context {} fails with capability_unavailable and details.selection_reason: "target_unsupported". The discovery result therefore identifies a different prerequisite from the actual call. trace_call_path correctly identifies the target restriction in the same inventory. The wrong navigation reason persists in binary_session {"detail":"complete"} after the failed call.

Reproduction

  1. Use the built baseline checkout with its locked dependencies installed and Node 24 on PATH. From its root, preserve the checkout location and enter a fresh empty working directory: source_checkout="$PWD"; repro_dir="$(mktemp -d)"; cd "$repro_dir".
  2. Save the following as reproduce.py in that empty directory. It creates the plist inline, redirects HOME and XDG state to local directories, and drives the production node scripts/rea.mjs --mcp entry point using newline-delimited JSON-RPC.
import json, os, pathlib, subprocess, sys, threading

repo = pathlib.Path(sys.argv[1]).resolve()
work = pathlib.Path.cwd()
(work / 'sample.plist').write_text('<?xml version="1.0" encoding="UTF-8"?>\n<plist version="1.0"><dict><key>CFBundleName</key><string>Example</string></dict></plist>\n')
env = {'PATH': os.environ['PATH'], 'REA_LOG_LEVEL': 'silent'}
for key in ['HOME', 'XDG_CONFIG_HOME', 'XDG_DATA_HOME', 'XDG_CACHE_HOME', 'XDG_STATE_HOME', 'TMPDIR']:
    directory = work / key.lower()
    directory.mkdir(exist_ok=True)
    env[key] = str(directory)
p = subprocess.Popen(['node', str(repo / 'scripts/rea.mjs'), '--mcp'], cwd=work,
                     env=env, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
                     stderr=subprocess.DEVNULL, text=True)
timer = threading.Timer(45, p.kill)
timer.start()

def send(message):
    p.stdin.write(json.dumps(message) + '\n')
    p.stdin.flush()

def request(i, method, params):
    send({'jsonrpc': '2.0', 'id': i, 'method': method, 'params': params})
    for line in p.stdout:
        reply = json.loads(line)
        if reply.get('id') == i:
            return reply['result']
    raise RuntimeError('server ended before response')

def call(i, name, arguments):
    reply = request(i, 'tools/call', {'name': name, 'arguments': arguments})
    return reply, json.loads(reply['content'][0]['text'])

def show(body, phase):
    status = body['result']
    for name in ['get_navigation_context', 'trace_call_path']:
        group = next(g for g in status['tool_availability']['groups'] if name in g['names'])
        print(json.dumps({'phase': phase, 'name': name, 'open': status['open'],
                          **{k: group[k] for k in ['available', 'reason', 'remediation']}}))

try:
    request(1, 'initialize', {'protocolVersion': '2025-11-25', 'capabilities': {},
            'clientInfo': {'name': 'navigation-repro', 'version': '1'}})
    send({'jsonrpc': '2.0', 'method': 'notifications/initialized'})
    show(call(2, 'binary_session', {})[1], 'before-open')
    reply, body = call(3, 'open_binary', {'path': str(work / 'sample.plist')})
    print(json.dumps({'open_isError': reply.get('isError', False),
                      'target': {k: body['result'][k] for k in ['kind', 'format']}}))
    show(call(4, 'binary_session', {})[1], 'after-open')
    reply, body = call(5, 'get_navigation_context', {})
    error = body['error']
    print(json.dumps({'navigation_isError': reply.get('isError', False),
                      'code': error['code'], 'category': error['category'],
                      'selection_reason': error['details']['selection_reason'],
                      'operation': error['details']['operation']}))
    show(call(6, 'binary_session', {'detail': 'complete'})[1], 'after-call-complete')
finally:
    p.stdin.close()
    try:
        p.wait(timeout=10)
    except subprocess.TimeoutExpired:
        p.terminate()
        p.wait(timeout=10)
    timer.cancel()
    print(json.dumps({'server_exit': p.returncode}))
  1. Run python3 reproduce.py "$source_checkout". The relevant observed output is reproduced below. Repeat in another fresh directory with the same script and checkout to compare results.
{"phase": "before-open", "name": "get_navigation_context", "open": false, "available": false, "reason": "target_required", "remediation": "Call open_binary with a supported local target."}
{"phase": "before-open", "name": "trace_call_path", "open": false, "available": false, "reason": "target_required", "remediation": "Call open_binary with a supported local target."}
{"open_isError": false, "target": {"kind": "artifact", "format": "plist"}}
{"phase": "after-open", "name": "get_navigation_context", "open": true, "available": false, "reason": "provider_missing", "remediation": "Configure a provider for one of: current_document, current_address, resolve_containing_procedure."}
{"phase": "after-open", "name": "trace_call_path", "open": true, "available": false, "reason": "target_unsupported", "remediation": "Inventory or extract a native executable, then call open_binary on that executable."}
{"navigation_isError": true, "code": "capability_unavailable", "category": "unsupported_provider", "selection_reason": "target_unsupported", "operation": "current_document"}
{"phase": "after-call-complete", "name": "get_navigation_context", "open": true, "available": false, "reason": "provider_missing", "remediation": "Configure a provider for one of: current_document, current_address, resolve_containing_procedure."}
{"phase": "after-call-complete", "name": "trace_call_path", "open": true, "available": false, "reason": "target_unsupported", "remediation": "Inventory or extract a native executable, then call open_binary on that executable."}
{"server_exit": 0}

The two complete minimal-script attempts both reproduced the mismatch, produced byte-identical displayed output, and ended with server exit code 0. No native analysis engine or external analysis API was needed.

Expected behavior

Navigation should stay unavailable for this target, and discovery should explain that the current target is unsupported rather than direct the caller to configure providers. The typed invocation diagnostic and the sibling inventory entry already expose that restriction. Preserve the correct target_required result before opening a target.

The contract in docs/mcp-contracts.md, heading "Identity and discovery", says: "Call binary_session with {} and read result.tool_availability to choose a callable operation for the current target, provider, host, and negotiated client capabilities." The same paragraph requires carrying each group's reason and remediation into the individual tool entries. These are current target/provider facts; the expectation does not assert that every request or selector must succeed.

Impact and scope

For the tested plist, both discovery projections provide the wrong machine-readable recovery classification. Calling navigation directly still exposes a usable target-support diagnosis, and the sibling traversal entry provides target-changing guidance. The observed defect is bounded to discovery and recovery information; downstream agent decisions, provider-configuration attempts, other target formats and other hosts were not measured. Successful navigation after opening a native executable was not tested.

REA version

6.8.0 (source commit a905a71e31)

Runtime and platform

Node 24.22.1; macOS 27.0.1 arm64; production stdio MCP with isolated HOME/XDG/TMPDIR.

Target and artifact context

Generated XML property list containing one CFBundleName entry; open_binary reports kind artifact and format plist. Default automatic provider selection with no user configuration inherited.

Interface

MCP

Earliest failure stage

Transport or result projection

Relevant output or evidence

Observed through the production stdio server: the complete output above shows a passing no-target control, successful plist admission, provider_missing navigation discovery, target_unsupported traversal discovery, the immediate navigation refusal, unchanged complete discovery, and clean server termination. Both independent replays returned identical displayed results. A separate comprehensive replay also observed default_mode_available: false and provider-configuration advice in both navigation modes.

Source-only (not executed as separate tests): src/server/registerSessionStatusTool.ts:133 constructs the capability inventory for the public status response; src/application/CapabilityInventory.ts:389 composes navigation's provider modes and overlays target availability; src/contracts/sessionToolContracts.ts:171 declares navigation as a session-kind tool requiring an active target. The runtime's corresponding compiled branches were inspected and agree with these source conditions.

Typed outcome or recovery expected

With this plist open, report navigation unavailable with the target-support restriction, such as reason: "target_unsupported", and recovery consistent with the supported-target guidance. Keep mode facts coherent with that restriction. The no-target control must continue reporting target_required. This is a proposed restoration check; no patch was made or tested.

Analysis or suspected cause

Source-only (not executed as separate tests): the unsupported-target-kind branch at src/application/CapabilityInventory.ts:770 covers official-proxy and enhanced tools, excluding session-kind navigation. Its already-invoked target decision therefore does not replace the missing-provider result produced by prerequisite composition. The aggregate provider-configuration recommendation is constructed at src/application/CapabilityInventory.ts:976.

The executed refusal agrees with a different source owner: src/application/AnalysisContextQueries.ts:41 requests current_document for empty navigation arguments; src/application/binary/BinarySessionExecution.ts:69 routes an unavailable capability to the unbound-selection diagnostic; src/application/binary/AnalysisProviderRegistry.ts:551 classifies the selection as target_unsupported when all candidates reject the target. This explains the disagreement without assuming malformed provider output or an SDK validation failure.

Additional context

Related (closed):
https://github.com/morluto/rea/issues/5729 fixed navigation's missing-target discovery reason; the passing before-open control shows that fix is present, while this open-plist target still reproduces the distinct target-support defect on the baseline.

Open https://github.com/morluto/rea/issues/7310 concerns inspect_address_context before a target is open. Its no-target readiness correction can pass independently of this navigation report's open-unsupported-target recovery check. The related proposed session-prerequisite fixes do not change navigation's unsupported-target-kind branch.

The live npm registry query npm view rea-agents version returned 6.8.0, matching this baseline's package and initialized server version. The first affected revision and a last known good version were not established.

Originally created by @coygeek on GitHub (Oct 27, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/7397 ### 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 After `open_binary` successfully opens a generated XML plist as an artifact, `binary_session {}` says `get_navigation_context` is unavailable because of `provider_missing` and recommends configuring provider operations. On the same connection, `get_navigation_context {}` fails with `capability_unavailable` and `details.selection_reason: "target_unsupported"`. The discovery result therefore identifies a different prerequisite from the actual call. `trace_call_path` correctly identifies the target restriction in the same inventory. The wrong navigation reason persists in `binary_session {"detail":"complete"}` after the failed call. ### Reproduction 1. Use the built baseline checkout with its locked dependencies installed and Node 24 on PATH. From its root, preserve the checkout location and enter a fresh empty working directory: `source_checkout="$PWD"; repro_dir="$(mktemp -d)"; cd "$repro_dir"`. 2. Save the following as `reproduce.py` in that empty directory. It creates the plist inline, redirects HOME and XDG state to local directories, and drives the production `node scripts/rea.mjs --mcp` entry point using newline-delimited JSON-RPC. ```python import json, os, pathlib, subprocess, sys, threading repo = pathlib.Path(sys.argv[1]).resolve() work = pathlib.Path.cwd() (work / 'sample.plist').write_text('<?xml version="1.0" encoding="UTF-8"?>\n<plist version="1.0"><dict><key>CFBundleName</key><string>Example</string></dict></plist>\n') env = {'PATH': os.environ['PATH'], 'REA_LOG_LEVEL': 'silent'} for key in ['HOME', 'XDG_CONFIG_HOME', 'XDG_DATA_HOME', 'XDG_CACHE_HOME', 'XDG_STATE_HOME', 'TMPDIR']: directory = work / key.lower() directory.mkdir(exist_ok=True) env[key] = str(directory) p = subprocess.Popen(['node', str(repo / 'scripts/rea.mjs'), '--mcp'], cwd=work, env=env, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True) timer = threading.Timer(45, p.kill) timer.start() def send(message): p.stdin.write(json.dumps(message) + '\n') p.stdin.flush() def request(i, method, params): send({'jsonrpc': '2.0', 'id': i, 'method': method, 'params': params}) for line in p.stdout: reply = json.loads(line) if reply.get('id') == i: return reply['result'] raise RuntimeError('server ended before response') def call(i, name, arguments): reply = request(i, 'tools/call', {'name': name, 'arguments': arguments}) return reply, json.loads(reply['content'][0]['text']) def show(body, phase): status = body['result'] for name in ['get_navigation_context', 'trace_call_path']: group = next(g for g in status['tool_availability']['groups'] if name in g['names']) print(json.dumps({'phase': phase, 'name': name, 'open': status['open'], **{k: group[k] for k in ['available', 'reason', 'remediation']}})) try: request(1, 'initialize', {'protocolVersion': '2025-11-25', 'capabilities': {}, 'clientInfo': {'name': 'navigation-repro', 'version': '1'}}) send({'jsonrpc': '2.0', 'method': 'notifications/initialized'}) show(call(2, 'binary_session', {})[1], 'before-open') reply, body = call(3, 'open_binary', {'path': str(work / 'sample.plist')}) print(json.dumps({'open_isError': reply.get('isError', False), 'target': {k: body['result'][k] for k in ['kind', 'format']}})) show(call(4, 'binary_session', {})[1], 'after-open') reply, body = call(5, 'get_navigation_context', {}) error = body['error'] print(json.dumps({'navigation_isError': reply.get('isError', False), 'code': error['code'], 'category': error['category'], 'selection_reason': error['details']['selection_reason'], 'operation': error['details']['operation']})) show(call(6, 'binary_session', {'detail': 'complete'})[1], 'after-call-complete') finally: p.stdin.close() try: p.wait(timeout=10) except subprocess.TimeoutExpired: p.terminate() p.wait(timeout=10) timer.cancel() print(json.dumps({'server_exit': p.returncode})) ``` 3. Run `python3 reproduce.py "$source_checkout"`. The relevant observed output is reproduced below. Repeat in another fresh directory with the same script and checkout to compare results. ```json {"phase": "before-open", "name": "get_navigation_context", "open": false, "available": false, "reason": "target_required", "remediation": "Call open_binary with a supported local target."} {"phase": "before-open", "name": "trace_call_path", "open": false, "available": false, "reason": "target_required", "remediation": "Call open_binary with a supported local target."} {"open_isError": false, "target": {"kind": "artifact", "format": "plist"}} {"phase": "after-open", "name": "get_navigation_context", "open": true, "available": false, "reason": "provider_missing", "remediation": "Configure a provider for one of: current_document, current_address, resolve_containing_procedure."} {"phase": "after-open", "name": "trace_call_path", "open": true, "available": false, "reason": "target_unsupported", "remediation": "Inventory or extract a native executable, then call open_binary on that executable."} {"navigation_isError": true, "code": "capability_unavailable", "category": "unsupported_provider", "selection_reason": "target_unsupported", "operation": "current_document"} {"phase": "after-call-complete", "name": "get_navigation_context", "open": true, "available": false, "reason": "provider_missing", "remediation": "Configure a provider for one of: current_document, current_address, resolve_containing_procedure."} {"phase": "after-call-complete", "name": "trace_call_path", "open": true, "available": false, "reason": "target_unsupported", "remediation": "Inventory or extract a native executable, then call open_binary on that executable."} {"server_exit": 0} ``` The two complete minimal-script attempts both reproduced the mismatch, produced byte-identical displayed output, and ended with server exit code 0. No native analysis engine or external analysis API was needed. ### Expected behavior Navigation should stay unavailable for this target, and discovery should explain that the current target is unsupported rather than direct the caller to configure providers. The typed invocation diagnostic and the sibling inventory entry already expose that restriction. Preserve the correct `target_required` result before opening a target. The contract in `docs/mcp-contracts.md`, heading "Identity and discovery", says: "Call `binary_session` with `{}` and read `result.tool_availability` to choose a callable operation for the current target, provider, host, and negotiated client capabilities." The same paragraph requires carrying each group's reason and remediation into the individual tool entries. These are current target/provider facts; the expectation does not assert that every request or selector must succeed. ### Impact and scope For the tested plist, both discovery projections provide the wrong machine-readable recovery classification. Calling navigation directly still exposes a usable target-support diagnosis, and the sibling traversal entry provides target-changing guidance. The observed defect is bounded to discovery and recovery information; downstream agent decisions, provider-configuration attempts, other target formats and other hosts were not measured. Successful navigation after opening a native executable was not tested. ### REA version 6.8.0 (source commit a905a71e31f71457cb21fd056b234f872126974e) ### Runtime and platform Node 24.22.1; macOS 27.0.1 arm64; production stdio MCP with isolated HOME/XDG/TMPDIR. ### Target and artifact context Generated XML property list containing one CFBundleName entry; open_binary reports kind artifact and format plist. Default automatic provider selection with no user configuration inherited. ### Interface MCP ### Earliest failure stage Transport or result projection ### Relevant output or evidence Observed through the production stdio server: the complete output above shows a passing no-target control, successful plist admission, provider_missing navigation discovery, target_unsupported traversal discovery, the immediate navigation refusal, unchanged complete discovery, and clean server termination. Both independent replays returned identical displayed results. A separate comprehensive replay also observed `default_mode_available: false` and provider-configuration advice in both navigation modes. Source-only (not executed as separate tests): `src/server/registerSessionStatusTool.ts:133` constructs the capability inventory for the public status response; `src/application/CapabilityInventory.ts:389` composes navigation's provider modes and overlays target availability; `src/contracts/sessionToolContracts.ts:171` declares navigation as a session-kind tool requiring an active target. The runtime's corresponding compiled branches were inspected and agree with these source conditions. ### Typed outcome or recovery expected With this plist open, report navigation unavailable with the target-support restriction, such as `reason: "target_unsupported"`, and recovery consistent with the supported-target guidance. Keep mode facts coherent with that restriction. The no-target control must continue reporting `target_required`. This is a proposed restoration check; no patch was made or tested. ### Analysis or suspected cause Source-only (not executed as separate tests): the unsupported-target-kind branch at `src/application/CapabilityInventory.ts:770` covers official-proxy and enhanced tools, excluding session-kind navigation. Its already-invoked target decision therefore does not replace the missing-provider result produced by prerequisite composition. The aggregate provider-configuration recommendation is constructed at `src/application/CapabilityInventory.ts:976`. The executed refusal agrees with a different source owner: `src/application/AnalysisContextQueries.ts:41` requests current_document for empty navigation arguments; `src/application/binary/BinarySessionExecution.ts:69` routes an unavailable capability to the unbound-selection diagnostic; `src/application/binary/AnalysisProviderRegistry.ts:551` classifies the selection as target_unsupported when all candidates reject the target. This explains the disagreement without assuming malformed provider output or an SDK validation failure. ### Additional context Related (closed): https://github.com/morluto/rea/issues/5729 fixed navigation's missing-target discovery reason; the passing before-open control shows that fix is present, while this open-plist target still reproduces the distinct target-support defect on the baseline. Open https://github.com/morluto/rea/issues/7310 concerns inspect_address_context before a target is open. Its no-target readiness correction can pass independently of this navigation report's open-unsupported-target recovery check. The related proposed session-prerequisite fixes do not change navigation's unsupported-target-kind branch. The live npm registry query `npm view rea-agents version` returned 6.8.0, matching this baseline's package and initialized server version. The first affected revision and a last known good version were not established.
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#4404
No description provided.