[GH-ISSUE #3183] [Bug] Matching tilde or empty Cutter bridge directory settings hide live sessions #1120

Closed
opened 2026-10-15 18:31:15 +00:00 by bsakaguchi · 2 comments
Owner

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

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

list-cutter-sessions returns no sessions when the production Python bridge and CLI receive the same tilde-relative or empty REA_CUTTER_BRIDGE_DIR, even though the bridge has published a private descriptor and accepts authenticated requests. With REA_CUTTER_BRIDGE_DIR='~/bridge-repro', the CLI reports the literal bridge_directory: "~/bridge-repro". With an empty value, it reports bridge_directory: "". Both return discovery_status: "no_live_bridge_found", bridge_directory_security: "not_checked", and exit code 0. A command using the running producer's valid session ID and generation fails with invalid_request / invalid_input and exit code 1.

Changing only the CLI setting to the producer's resolved absolute directory immediately discovers that same running session with sessions_found and private_verified. The same fixture command then completes with exit code 0. Absolute and unset configurations also pass with matching producer/client configuration.

Reproduction

  1. Start from a clean checkout of morluto/rea at 58300885207133422af235e0f2b10658cf958b03, use the repository's pinned toolchain, and prepare the runtime from the checkout root:
npm ci
npm run build:cached

These preparation commands were not rerun by the verifier, who used the checkout's prepared dependencies and compiled baseline runtime. Python 3.14.7 was available for the producer fixture.

  1. From the checkout root, run this POSIX-shell reproduction. It creates an isolated home, starts the existing fixture that imports the production Python bridge server, waits for its private descriptor, and exercises the supported CLI against that still-running producer. Each configuration also gets an absolute-directory control. Descriptor tokens never enter printed output.
mkdir -p .repro-cutter/home
HOME="$PWD/.repro-cutter/home" \
XDG_CONFIG_HOME="$PWD/.repro-cutter/home/config" \
XDG_CACHE_HOME="$PWD/.repro-cutter/home/cache" \
XDG_DATA_HOME="$PWD/.repro-cutter/home/data" \
PYTHONDONTWRITEBYTECODE=1 python3 - <<'PY'
import json
import os
from pathlib import Path
import select
import subprocess

root = Path.cwd()
home = Path(os.environ['HOME'])
for key in ('XDG_CONFIG_HOME', 'XDG_CACHE_HOME', 'XDG_DATA_HOME'):
    Path(os.environ[key]).mkdir(parents=True, exist_ok=True)
base = dict(os.environ, PYTHONDONTWRITEBYTECODE='1')

def cli(env, *args):
    result = subprocess.run(
        ['node', 'scripts/rea.mjs', *args, '--format', 'json'],
        cwd=root, env=env, capture_output=True, text=True, timeout=15,
    )
    assert result.stdout, (result.returncode, result.stderr)
    return result.returncode, json.loads(result.stdout)

cases = [('tilde', '~/bridge-repro'), ('empty', ''),
         ('absolute', str(home / 'bridge-absolute')), ('unset', None)]
for name, value in cases:
    env = dict(base)
    if value is None:
        env.pop('REA_CUTTER_BRIDGE_DIR', None)
    else:
        env['REA_CUTTER_BRIDGE_DIR'] = value
    if name == 'tilde':
        resolved = home / 'bridge-repro'
    elif name == 'absolute':
        resolved = home / 'bridge-absolute'
    else:
        resolved = home / '.cache/rea/cutter-bridge'
    producer = subprocess.Popen(
        ['python3', 'tests/fixtures/cutter_bridge_protocol_test.py', '--serve'],
        cwd=root, env=env, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
        stderr=subprocess.PIPE, text=True,
    )
    try:
        assert select.select([producer.stdout], [], [], 8)[0], 'not ready'
        assert producer.stdout.readline().strip() == 'ready'
        files = list(resolved.glob('cutter-*.json'))
        assert len(files) == 1, 'expected one published descriptor'
        assert resolved.stat().st_mode & 0o777 == 0o700
        assert files[0].stat().st_mode & 0o777 == 0o600
        descriptor = json.loads(files[0].read_text())
        status, discovery = cli(env, 'list-cutter-sessions')
        absolute_env = dict(env, REA_CUTTER_BRIDGE_DIR=str(resolved))
        control_status, control = cli(absolute_env, 'list-cutter-sessions')
        assert control_status == 0 and len(control['sessions']) == 1
        assert control['sessions'][0]['session_id'] == descriptor['session_id']
        args = ('cutter-command', descriptor['session_id'],
                str(descriptor['document_generation']), 'iI')
        command_status, command = cli(env, *args)
        passing_status, passing = cli(absolute_env, *args)
        assert passing_status == 0
        assert passing['normalized_result']['execution_state'] == 'complete'
        print(json.dumps({
            'case': name, 'discovery_exit': status,
            'session_count': len(discovery['sessions']),
            'discovery_status': discovery['discovery_status'],
            'security': discovery['bridge_directory_security'],
            'directory_matches_producer': discovery['bridge_directory'] == str(resolved),
            'command_exit': command_status, 'command_code': command.get('code'),
            'absolute_control_sessions': len(control['sessions']),
            'absolute_control_command_exit': passing_status,
        }), flush=True)
    finally:
        try:
            out, error = producer.communicate('shutdown\n', timeout=5)
        except subprocess.TimeoutExpired:
            producer.kill()
            out, error = producer.communicate(timeout=5)
        assert producer.returncode == 0, error
        assert not list(resolved.glob('cutter-*.json')), 'descriptor retained'
PY
  1. Observe that the tilde and empty cases return zero sessions and command exit 1, while each absolute-directory control finds one matching session and completes the command. The absolute and unset cases pass with matching producer/client configuration.

  2. After the script has shut down its producers, remove its local reproduction inputs:

rm -rf .repro-cutter

Expected behavior

src/cli/cutterCommands.ts:20-26 advertises listing live Cutter instances connected through the REA Python plugin bridge. The list_cutter_sessions entry in src/contracts/cutterToolContracts.ts gives the corresponding tool contract. docs/reverse-engineering-tools.md:60-80 describes the authenticated local bridge; it does not independently specify environment-variable expansion.

With the same environment setting, discovery should find the session whose descriptor the bridge published. The configured tilde expansion and empty-value fallback come from the producer's implemented configuration contract in bridge/cutter/rea_bridge.py:308-315. The client should agree with that directory resolution, discover the same session, and allow the same session ID and generation to complete the command that succeeds with the resolved absolute directory.

Impact and scope

The observed CLI workflow cannot discover or command a running bridge under tilde-relative or empty directory settings. Setting the directory to the producer's resolved absolute path is a verified practical workaround that retains private-directory and authenticated-transport checks. The producer directory had mode 0700 and its descriptor had mode 0600 in both failing cases. Absolute and unset configurations succeeded on the verified host.

The reproduction uses the production Python bridge server with the repository fixture's Cutter and Qt/UI stubs. It does not establish real Cutter GUI or engine behavior. MCP shares the client by source inspection but was not independently executed. Windows behavior was not tested. No data loss or security boundary crossing was observed.

REA version

6.4.0 (main at 5830088520)

Runtime and platform

Node.js v26.10.0; Python 3.14.7; macOS. The verifier did not record the macOS release or architecture.

Target and artifact context

The repository's tests/fixtures/cutter_bridge_protocol_test.py --serve producer imports the production Python bridge and uses a fake Cutter/UI dispatcher on loopback. The inputs are an isolated home, directory settings, and generated private session descriptors. No binary target or real Cutter GUI was used.

Interface

CLI

Earliest failure stage

Setup or provider admission

Relevant output or evidence

case      sessions  discovery_status       security          command_exit  command_code     absolute_control_sessions  absolute_control_command_exit
tilde     0         no_live_bridge_found   not_checked       1             invalid_request  1                          0
empty     0         no_live_bridge_found   not_checked       1             invalid_request  1                          0
absolute  1         sessions_found         private_verified  0             null             1                          0
unset     1         sessions_found         private_verified  0             null             1                          0

These are the verifier's observed case summaries, reduced to the relevant fields. Each absolute control used the same still-running producer as its corresponding original configuration.

Typed outcome or recovery expected

For the original tilde-relative and empty settings, discovery should report the producer's resolved directory, list the exact running session with sessions_found and private_verified, and permit its valid session ID and generation to complete the fixture command. Keep the passing absolute and unset controls, private descriptor/directory requirements, and authenticated peer checks. Changing only the reported directory without discovering and communicating with the producer would not restore the contract.

Analysis or suspected cause

Source-derived at the baseline: src/cutter/CutterBridgeClient.ts:254,281-285 retains configured ?? default, so a tilde-relative value stays literal and an empty string bypasses the default. The production Python resolver in bridge/cutter/rea_bridge.py:308-315 expands a nonempty configured value and selects the default for an empty value. The production plugin starts this server in bridge/cutter/rea_bridge.py:760-763.

Source-derived: src/cutter/CutterBridgeClient.ts:639-645 calls lstat on the retained value and turns ENOENT into empty discovery. src/cutter/CutterBridgeService.ts:171-190 maps the resulting unavailable/stale session error to the observed caller-visible input error. The CLI passes its environment into this client at src/cli/cutterCommands.ts:18. These source paths explain the directory disagreement reproduced by the producer/CLI comparison.

Additional context

P2 is supported because the affected configuration inputs repeatedly hide a running bridge and prevent command access, while the verified absolute-directory workaround preserves the same session and authenticated transport.

Related: #3182 reports a separate failure in the same area; the two have independent fixes.

Originally created by @coygeek on GitHub (Oct 14, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3183 ### 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 `list-cutter-sessions` returns no sessions when the production Python bridge and CLI receive the same tilde-relative or empty `REA_CUTTER_BRIDGE_DIR`, even though the bridge has published a private descriptor and accepts authenticated requests. With `REA_CUTTER_BRIDGE_DIR='~/bridge-repro'`, the CLI reports the literal `bridge_directory: "~/bridge-repro"`. With an empty value, it reports `bridge_directory: ""`. Both return `discovery_status: "no_live_bridge_found"`, `bridge_directory_security: "not_checked"`, and exit code 0. A command using the running producer's valid session ID and generation fails with `invalid_request` / `invalid_input` and exit code 1. Changing only the CLI setting to the producer's resolved absolute directory immediately discovers that same running session with `sessions_found` and `private_verified`. The same fixture command then completes with exit code 0. Absolute and unset configurations also pass with matching producer/client configuration. ### Reproduction 1. Start from a clean checkout of `morluto/rea` at `58300885207133422af235e0f2b10658cf958b03`, use the repository's pinned toolchain, and prepare the runtime from the checkout root: ```sh npm ci npm run build:cached ``` These preparation commands were not rerun by the verifier, who used the checkout's prepared dependencies and compiled baseline runtime. Python 3.14.7 was available for the producer fixture. 2. From the checkout root, run this POSIX-shell reproduction. It creates an isolated home, starts the existing fixture that imports the production Python bridge server, waits for its private descriptor, and exercises the supported CLI against that still-running producer. Each configuration also gets an absolute-directory control. Descriptor tokens never enter printed output. ```sh mkdir -p .repro-cutter/home HOME="$PWD/.repro-cutter/home" \ XDG_CONFIG_HOME="$PWD/.repro-cutter/home/config" \ XDG_CACHE_HOME="$PWD/.repro-cutter/home/cache" \ XDG_DATA_HOME="$PWD/.repro-cutter/home/data" \ PYTHONDONTWRITEBYTECODE=1 python3 - <<'PY' import json import os from pathlib import Path import select import subprocess root = Path.cwd() home = Path(os.environ['HOME']) for key in ('XDG_CONFIG_HOME', 'XDG_CACHE_HOME', 'XDG_DATA_HOME'): Path(os.environ[key]).mkdir(parents=True, exist_ok=True) base = dict(os.environ, PYTHONDONTWRITEBYTECODE='1') def cli(env, *args): result = subprocess.run( ['node', 'scripts/rea.mjs', *args, '--format', 'json'], cwd=root, env=env, capture_output=True, text=True, timeout=15, ) assert result.stdout, (result.returncode, result.stderr) return result.returncode, json.loads(result.stdout) cases = [('tilde', '~/bridge-repro'), ('empty', ''), ('absolute', str(home / 'bridge-absolute')), ('unset', None)] for name, value in cases: env = dict(base) if value is None: env.pop('REA_CUTTER_BRIDGE_DIR', None) else: env['REA_CUTTER_BRIDGE_DIR'] = value if name == 'tilde': resolved = home / 'bridge-repro' elif name == 'absolute': resolved = home / 'bridge-absolute' else: resolved = home / '.cache/rea/cutter-bridge' producer = subprocess.Popen( ['python3', 'tests/fixtures/cutter_bridge_protocol_test.py', '--serve'], cwd=root, env=env, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, ) try: assert select.select([producer.stdout], [], [], 8)[0], 'not ready' assert producer.stdout.readline().strip() == 'ready' files = list(resolved.glob('cutter-*.json')) assert len(files) == 1, 'expected one published descriptor' assert resolved.stat().st_mode & 0o777 == 0o700 assert files[0].stat().st_mode & 0o777 == 0o600 descriptor = json.loads(files[0].read_text()) status, discovery = cli(env, 'list-cutter-sessions') absolute_env = dict(env, REA_CUTTER_BRIDGE_DIR=str(resolved)) control_status, control = cli(absolute_env, 'list-cutter-sessions') assert control_status == 0 and len(control['sessions']) == 1 assert control['sessions'][0]['session_id'] == descriptor['session_id'] args = ('cutter-command', descriptor['session_id'], str(descriptor['document_generation']), 'iI') command_status, command = cli(env, *args) passing_status, passing = cli(absolute_env, *args) assert passing_status == 0 assert passing['normalized_result']['execution_state'] == 'complete' print(json.dumps({ 'case': name, 'discovery_exit': status, 'session_count': len(discovery['sessions']), 'discovery_status': discovery['discovery_status'], 'security': discovery['bridge_directory_security'], 'directory_matches_producer': discovery['bridge_directory'] == str(resolved), 'command_exit': command_status, 'command_code': command.get('code'), 'absolute_control_sessions': len(control['sessions']), 'absolute_control_command_exit': passing_status, }), flush=True) finally: try: out, error = producer.communicate('shutdown\n', timeout=5) except subprocess.TimeoutExpired: producer.kill() out, error = producer.communicate(timeout=5) assert producer.returncode == 0, error assert not list(resolved.glob('cutter-*.json')), 'descriptor retained' PY ``` 3. Observe that the tilde and empty cases return zero sessions and command exit 1, while each absolute-directory control finds one matching session and completes the command. The absolute and unset cases pass with matching producer/client configuration. 4. After the script has shut down its producers, remove its local reproduction inputs: ```sh rm -rf .repro-cutter ``` ### Expected behavior `src/cli/cutterCommands.ts:20-26` advertises listing live Cutter instances connected through the REA Python plugin bridge. The `list_cutter_sessions` entry in `src/contracts/cutterToolContracts.ts` gives the corresponding tool contract. `docs/reverse-engineering-tools.md:60-80` describes the authenticated local bridge; it does not independently specify environment-variable expansion. With the same environment setting, discovery should find the session whose descriptor the bridge published. The configured tilde expansion and empty-value fallback come from the producer's implemented configuration contract in `bridge/cutter/rea_bridge.py:308-315`. The client should agree with that directory resolution, discover the same session, and allow the same session ID and generation to complete the command that succeeds with the resolved absolute directory. ### Impact and scope The observed CLI workflow cannot discover or command a running bridge under tilde-relative or empty directory settings. Setting the directory to the producer's resolved absolute path is a verified practical workaround that retains private-directory and authenticated-transport checks. The producer directory had mode 0700 and its descriptor had mode 0600 in both failing cases. Absolute and unset configurations succeeded on the verified host. The reproduction uses the production Python bridge server with the repository fixture's Cutter and Qt/UI stubs. It does not establish real Cutter GUI or engine behavior. MCP shares the client by source inspection but was not independently executed. Windows behavior was not tested. No data loss or security boundary crossing was observed. ### REA version 6.4.0 (main at 58300885207133422af235e0f2b10658cf958b03) ### Runtime and platform Node.js v26.10.0; Python 3.14.7; macOS. The verifier did not record the macOS release or architecture. ### Target and artifact context The repository's `tests/fixtures/cutter_bridge_protocol_test.py --serve` producer imports the production Python bridge and uses a fake Cutter/UI dispatcher on loopback. The inputs are an isolated home, directory settings, and generated private session descriptors. No binary target or real Cutter GUI was used. ### Interface CLI ### Earliest failure stage Setup or provider admission ### Relevant output or evidence ```text case sessions discovery_status security command_exit command_code absolute_control_sessions absolute_control_command_exit tilde 0 no_live_bridge_found not_checked 1 invalid_request 1 0 empty 0 no_live_bridge_found not_checked 1 invalid_request 1 0 absolute 1 sessions_found private_verified 0 null 1 0 unset 1 sessions_found private_verified 0 null 1 0 ``` These are the verifier's observed case summaries, reduced to the relevant fields. Each absolute control used the same still-running producer as its corresponding original configuration. ### Typed outcome or recovery expected For the original tilde-relative and empty settings, discovery should report the producer's resolved directory, list the exact running session with `sessions_found` and `private_verified`, and permit its valid session ID and generation to complete the fixture command. Keep the passing absolute and unset controls, private descriptor/directory requirements, and authenticated peer checks. Changing only the reported directory without discovering and communicating with the producer would not restore the contract. ### Analysis or suspected cause Source-derived at the baseline: `src/cutter/CutterBridgeClient.ts:254,281-285` retains `configured ?? default`, so a tilde-relative value stays literal and an empty string bypasses the default. The production Python resolver in `bridge/cutter/rea_bridge.py:308-315` expands a nonempty configured value and selects the default for an empty value. The production plugin starts this server in `bridge/cutter/rea_bridge.py:760-763`. Source-derived: `src/cutter/CutterBridgeClient.ts:639-645` calls `lstat` on the retained value and turns ENOENT into empty discovery. `src/cutter/CutterBridgeService.ts:171-190` maps the resulting unavailable/stale session error to the observed caller-visible input error. The CLI passes its environment into this client at `src/cli/cutterCommands.ts:18`. These source paths explain the directory disagreement reproduced by the producer/CLI comparison. ### Additional context P2 is supported because the affected configuration inputs repeatedly hide a running bridge and prevent command access, while the verified absolute-directory workaround preserves the same session and authenticated transport. Related: #3182 reports a separate failure in the same area; the two have independent fixes.
bsakaguchi 2026-10-15 18:31:15 +00:00
  • closed this issue
  • added the
    bug
    label
Author
Owner

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

I'd like to take this one. Plan: expand a leading ~ in REA_CUTTER_BRIDGE_DIR and treat an empty value as unset in CutterBridgeClient, matching bridge/cutter/rea_bridge.py, with tests. PR coming soon.

<!-- gh-comment-id:6175128243 --> @sathariels commented on GitHub (Oct 14, 2026): I'd like to take this one. Plan: expand a leading `~` in `REA_CUTTER_BRIDGE_DIR` and treat an empty value as unset in `CutterBridgeClient`, matching `bridge/cutter/rea_bridge.py`, with tests. PR coming soon.
Author
Owner

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

Delivered on main in fffb07419.

Cutter consumer paths disagreed with publisher expansion, including empty values, tilde paths and the selected caller home. Resolution now uses the shared caller-home owner and producer-compatible default selection.

Validation: Authenticated descriptor discovery for tilde/default paths under an isolated caller HOME, plus Windows USERPROFILE precedence and absolute-path controls. No real Cutter GUI engine was run. Full exact-revision qualification passed.

<!-- gh-comment-id:6177622588 --> @morluto commented on GitHub (Oct 14, 2026): Delivered on main in [fffb07419](https://github.com/morluto/rea/commit/fffb0741936067530d1ce9777461cd53e87398c2). Cutter consumer paths disagreed with publisher expansion, including empty values, tilde paths and the selected caller home. Resolution now uses the shared caller-home owner and producer-compatible default selection. Validation: Authenticated descriptor discovery for tilde/default paths under an isolated caller HOME, plus Windows USERPROFILE precedence and absolute-path controls. No real Cutter GUI engine was run. Full exact-revision [qualification](https://github.com/morluto/rea/actions/runs/38629313022) passed.
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#1120
No description provided.