[GH-ISSUE #7639] [Bug] Module tracing drops a relative browser setting and reports it as missing #4565

Closed
opened 2026-10-31 00:50:50 +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/7639

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

With REA_BROWSER_EXECUTABLE=verify-c228-no-browser/chromium, running node scripts/rea.mjs trace-web-module-imports <manifest> 0 --format json against a synthetic, locally verified source exits 1 and returns capability_unavailable with reason missing_browser_executable. The complete response omits the supplied relative path.

Unsetting the variable produces exactly the same JSON, including the message and recovery instruction. Selecting an absolute path to a nonexistent executable instead returns execution_failure and retains that path and the missing-file explanation in diagnostics. All three cases were observed through the public CLI without starting a browser.

Reproduction

  1. From the built baseline checkout below, put Node.js 24.22.1 on PATH. Run this standalone Python program. It creates the manifest and source, isolates writable state, exercises all three selections twice, and removes its temporary directory on exit. No browser executable should exist at either selected location; the assertions check that condition.
python3 -I - <<'PYTHON'
import hashlib, json, os, pathlib, subprocess, tempfile

with tempfile.TemporaryDirectory() as directory:
    root = pathlib.Path(directory).resolve()
    env = {'PATH': os.environ['PATH']}
    for key in ['HOME', 'TMPDIR', 'XDG_CONFIG_HOME', 'XDG_CACHE_HOME',
                'XDG_DATA_HOME', 'XDG_STATE_HOME']:
        path = root / key.lower()
        path.mkdir()
        env[key] = str(path)
    source = b'import "./dep.js";\n'
    path = root / 'files' / 'fixture.test' / 'main.js'
    path.parent.mkdir(parents=True)
    path.write_bytes(source)
    complete = dict(
        status='complete_within_window', conditions=['complete_within_window'],
        policy_filtered_sections=[], attach_limited_sections=[],
        truncated_sections=[], unavailable_sections=[], excluded=[],
        dropped_events={key: 0 for key in [
            'scripts', 'network_requests', 'console_events',
            'websocket_connections', 'websocket_frames', 'webmcp_tools',
            'timeline_events', 'total']})
    manifest = dict(
        capture_path=str(root / 'capture.json'), capture_sha256='0' * 64,
        capture_kind='page-inspection', source_evidence_id=None,
        capture_completeness=complete, output_directory=str(root / 'export'),
        analysis_input=dict(input_path=str(root / 'files'), format='directory'),
        scripts=[dict(
            source=dict(kind='page-script', script_key='1', frame_id=None,
                        is_module=True, language='javascript', source_map_url=None),
            url='https://fixture.test/main.js',
            content=dict(state='exported', relative_path='fixture.test/main.js',
                         layout='url-path', layout_reason=None,
                         sha256=hashlib.sha256(source).hexdigest(),
                         bytes=len(source), media_type='text/javascript',
                         redacted=False, representation='debugger-source-utf8'))],
        limitations=[])
    manifest_path = root / 'manifest.json'
    manifest_path.write_text(json.dumps(manifest))
    relative = 'verify-c228-no-browser/chromium'
    absolute = str(root / 'missing' / 'chromium')
    assert not pathlib.Path(relative).exists()
    assert not pathlib.Path(absolute).exists()
    command = ['node', 'scripts/rea.mjs', 'trace-web-module-imports',
               str(manifest_path), '0', '--format', 'json']
    for attempt in range(2):
        bodies = {}
        for label, value in [('relative', relative), ('unset', None),
                             ('absolute-missing', absolute)]:
            selected = dict(env)
            if value is not None:
                selected['REA_BROWSER_EXECUTABLE'] = value
            result = subprocess.run(command, env=selected, capture_output=True,
                                    text=True, timeout=30)
            body = json.loads(result.stdout)
            bodies[label] = body
            assert result.returncode == 1 and result.stderr == ''
            retained = value in result.stdout if value is not None else None
            print(json.dumps(dict(attempt=attempt + 1, case=label,
                                  exit=result.returncode, code=body['code'],
                                  reason=body['details'].get('reason'),
                                  selected_value_present=retained)))
            if label == 'relative':
                assert retained is False
            if label == 'absolute-missing':
                assert body['details']['diagnostics']['executable'] == absolute
                assert "executable doesn't exist" in body['details']['diagnostics']['error_message']
        assert bodies['relative'] == bodies['unset']
    assert not pathlib.Path(relative).exists()
    assert not pathlib.Path(absolute).exists()
PYTHON
  1. Both rounds report capability_unavailable, missing_browser_executable, and selected_value_present: false for the relative selection. The unset response is identical. Both absolute controls report execution_failure and selected_value_present: true.

The standalone program above was run once, giving two attempts per case. An earlier independent replay also produced the same results twice per case. Every invocation exited 1 with empty stderr.

Expected behavior

Continue refusing a relative browser setting: literal native imports require an absolute, caller-supplied Chromium executable. The diagnostic should retain the selected relative value and identify it as supplied but invalid, so callers can distinguish that condition from an unset variable.

The repository's caller-input and failure-fact preservation rules apply to this non-sensitive executable selection. The current instruction already explains the absolute-path correction; the missing information is the rejected value and whether a selection was supplied. There is no established promise that the missing-browser diagnostic must use a particular replacement key or field layout.

Impact and scope

P3, supported under triage/v1: the defect loses diagnostic content for correctly rejected configuration. The message still identifies the variable and its absolute-path requirement. No valid input was accepted incorrectly, no valid browser workflow was shown to fail, and no analysis result or persistent state was corrupted.

Callers can inspect their launch configuration and supply an absolute path as directed. The absolute nonexistent control demonstrates that this branch retains a selected path and actual file-absence diagnostic; it does not demonstrate successful tracing with a working browser. Observed scope is the macOS CLI. MCP, other platforms, real-browser success, and effects on automated recovery were not exercised.

REA version

Exact stdout from node scripts/rea.mjs --version:

6.8.0

Source baseline: 65f1c447344a423fa2785847dbcea1e9a9e2454a.

Runtime and platform

Node.js v24.22.1; macOS 27.0.1; arm64. The existing built runtime and locked dependencies were used. HOME, TMPDIR and all four XDG writable-state roots were isolated. No browser or GUI application was launched.

Target and artifact context

A synthetic exported-script manifest and one 19-byte JavaScript source with a literal import. The manifest contains the source's actual SHA-256 and size. Its capture metadata is fixture data; there is no real capture or application execution. The import target need not exist because configuration admission fails before native resolution.

Interface

CLI

Earliest failure stage

Setup or provider admission

Relevant output or evidence

The relative and unset runs returned the following fields. The only omitted member here is details.partial_observation, which retains the fixture manifest/source locations, digests and sizes and contains no executable selection. The reproduction compares the complete JSON documents, including that member.

{
  "code": "capability_unavailable",
  "category": "unsupported_provider",
  "message": "Provide an absolute REA_BROWSER_EXECUTABLE for a caller-supplied Chromium browser supporting import.meta.resolve. REA installs no browser.",
  "retryable": false,
  "remediation": {
    "action": "Provide an absolute REA_BROWSER_EXECUTABLE for a caller-supplied Chromium browser supporting import.meta.resolve. REA installs no browser."
  },
  "details": {
    "provider_id": "native-module-resolver",
    "operation": "trace_web_module_imports",
    "reason": "missing_browser_executable"
  }
}

The absolute control instead returned details.diagnostics.executable equal to its selected nonexistent path, and details.diagnostics.error_message beginning browserType.launch: Failed to launch chromium because executable doesn't exist at, followed by that same path. No executable was present or created at either selection.

Contract locators at the baseline: AGENTS.md:13,19,33 require retaining caller-selected paths, omission and meaningful failure facts. docs/website-module-trace.md:11-21 shows caller-supplied Chromium with an absolute example. src/contracts/webScriptToolContracts.ts:47 explicitly requires absolute REA_BROWSER_EXECUTABLE for literal imports. The guide does not prescribe the exact missing-browser message or reason key.

Reachability locators: src/cli/webModuleCommands.ts:17-70, src/composition/webModules.ts:6-12, and src/application/WebModuleTraceService.ts:150-175 connect the public CLI and verified literal source to native resolution. src/browser/modules/NativeModuleResolver.ts:116-128 handles absent and relative values together and supplies neither value nor a distinguishing fact. Its absolute-launch failure path at :236-242 retains the executable and error message. The executed build contains the same configuration guard at dist/cli-runtime/cli.js:62888-62889.

Typed outcome or recovery expected

Keep the absolute-path requirement and non-retryable capability refusal. Retain the supplied relative spelling in a public diagnostic and distinguish rejected configuration from absence. The unset case should remain an absent-configuration refusal; the absolute missing-file case should continue retaining its selected executable and actual startup explanation.

Analysis or suspected cause

Source-derived explanation: a single guard combines an undefined environment value with any non-absolute string. It constructs the same capability error from a fixed reason and message, so the public projector has no rejected selection to retain. The later launcher catch explicitly attaches the executable and cause text, which explains the different absolute-control output.

Additional context

No same-defect report or fix was found in read-only searches of open and closed issues, issue comments, and open/closed/merged PRs.

Issue #7554 concerns browser-scenario startup failures for browser.executable_path; it does not cover this environment setting or native module tracing. Its restoration requires startup-cause projection after launch admission. This trace already retains absolute startup failures, while its relative-setting refusal loses the selection earlier.

The omissions in #7595 (WABT), #7569 (Wireshark), and #7594 (Frida) occur at different provider boundaries. #2180 and merged PR #2294 cover named input-contract correction metadata in other CLI commands. #5103 covers scenario JSON path normalization. None establishes a previous fix of this NativeModuleResolver configuration branch; no regression claim is made.

Originally created by @coygeek on GitHub (Oct 28, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/7639 ### 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 With `REA_BROWSER_EXECUTABLE=verify-c228-no-browser/chromium`, running `node scripts/rea.mjs trace-web-module-imports <manifest> 0 --format json` against a synthetic, locally verified source exits 1 and returns `capability_unavailable` with reason `missing_browser_executable`. The complete response omits the supplied relative path. Unsetting the variable produces exactly the same JSON, including the message and recovery instruction. Selecting an absolute path to a nonexistent executable instead returns `execution_failure` and retains that path and the missing-file explanation in diagnostics. All three cases were observed through the public CLI without starting a browser. ### Reproduction 1. From the built baseline checkout below, put Node.js 24.22.1 on PATH. Run this standalone Python program. It creates the manifest and source, isolates writable state, exercises all three selections twice, and removes its temporary directory on exit. No browser executable should exist at either selected location; the assertions check that condition. ```sh python3 -I - <<'PYTHON' import hashlib, json, os, pathlib, subprocess, tempfile with tempfile.TemporaryDirectory() as directory: root = pathlib.Path(directory).resolve() env = {'PATH': os.environ['PATH']} for key in ['HOME', 'TMPDIR', 'XDG_CONFIG_HOME', 'XDG_CACHE_HOME', 'XDG_DATA_HOME', 'XDG_STATE_HOME']: path = root / key.lower() path.mkdir() env[key] = str(path) source = b'import "./dep.js";\n' path = root / 'files' / 'fixture.test' / 'main.js' path.parent.mkdir(parents=True) path.write_bytes(source) complete = dict( status='complete_within_window', conditions=['complete_within_window'], policy_filtered_sections=[], attach_limited_sections=[], truncated_sections=[], unavailable_sections=[], excluded=[], dropped_events={key: 0 for key in [ 'scripts', 'network_requests', 'console_events', 'websocket_connections', 'websocket_frames', 'webmcp_tools', 'timeline_events', 'total']}) manifest = dict( capture_path=str(root / 'capture.json'), capture_sha256='0' * 64, capture_kind='page-inspection', source_evidence_id=None, capture_completeness=complete, output_directory=str(root / 'export'), analysis_input=dict(input_path=str(root / 'files'), format='directory'), scripts=[dict( source=dict(kind='page-script', script_key='1', frame_id=None, is_module=True, language='javascript', source_map_url=None), url='https://fixture.test/main.js', content=dict(state='exported', relative_path='fixture.test/main.js', layout='url-path', layout_reason=None, sha256=hashlib.sha256(source).hexdigest(), bytes=len(source), media_type='text/javascript', redacted=False, representation='debugger-source-utf8'))], limitations=[]) manifest_path = root / 'manifest.json' manifest_path.write_text(json.dumps(manifest)) relative = 'verify-c228-no-browser/chromium' absolute = str(root / 'missing' / 'chromium') assert not pathlib.Path(relative).exists() assert not pathlib.Path(absolute).exists() command = ['node', 'scripts/rea.mjs', 'trace-web-module-imports', str(manifest_path), '0', '--format', 'json'] for attempt in range(2): bodies = {} for label, value in [('relative', relative), ('unset', None), ('absolute-missing', absolute)]: selected = dict(env) if value is not None: selected['REA_BROWSER_EXECUTABLE'] = value result = subprocess.run(command, env=selected, capture_output=True, text=True, timeout=30) body = json.loads(result.stdout) bodies[label] = body assert result.returncode == 1 and result.stderr == '' retained = value in result.stdout if value is not None else None print(json.dumps(dict(attempt=attempt + 1, case=label, exit=result.returncode, code=body['code'], reason=body['details'].get('reason'), selected_value_present=retained))) if label == 'relative': assert retained is False if label == 'absolute-missing': assert body['details']['diagnostics']['executable'] == absolute assert "executable doesn't exist" in body['details']['diagnostics']['error_message'] assert bodies['relative'] == bodies['unset'] assert not pathlib.Path(relative).exists() assert not pathlib.Path(absolute).exists() PYTHON ``` 2. Both rounds report `capability_unavailable`, `missing_browser_executable`, and `selected_value_present: false` for the relative selection. The unset response is identical. Both absolute controls report `execution_failure` and `selected_value_present: true`. The standalone program above was run once, giving two attempts per case. An earlier independent replay also produced the same results twice per case. Every invocation exited 1 with empty stderr. ### Expected behavior Continue refusing a relative browser setting: literal native imports require an absolute, caller-supplied Chromium executable. The diagnostic should retain the selected relative value and identify it as supplied but invalid, so callers can distinguish that condition from an unset variable. The repository's caller-input and failure-fact preservation rules apply to this non-sensitive executable selection. The current instruction already explains the absolute-path correction; the missing information is the rejected value and whether a selection was supplied. There is no established promise that the missing-browser diagnostic must use a particular replacement key or field layout. ### Impact and scope P3, supported under triage/v1: the defect loses diagnostic content for correctly rejected configuration. The message still identifies the variable and its absolute-path requirement. No valid input was accepted incorrectly, no valid browser workflow was shown to fail, and no analysis result or persistent state was corrupted. Callers can inspect their launch configuration and supply an absolute path as directed. The absolute nonexistent control demonstrates that this branch retains a selected path and actual file-absence diagnostic; it does not demonstrate successful tracing with a working browser. Observed scope is the macOS CLI. MCP, other platforms, real-browser success, and effects on automated recovery were not exercised. ### REA version Exact stdout from `node scripts/rea.mjs --version`: ```text 6.8.0 ``` Source baseline: `65f1c447344a423fa2785847dbcea1e9a9e2454a`. ### Runtime and platform Node.js `v24.22.1`; macOS `27.0.1`; `arm64`. The existing built runtime and locked dependencies were used. HOME, TMPDIR and all four XDG writable-state roots were isolated. No browser or GUI application was launched. ### Target and artifact context A synthetic exported-script manifest and one 19-byte JavaScript source with a literal import. The manifest contains the source's actual SHA-256 and size. Its capture metadata is fixture data; there is no real capture or application execution. The import target need not exist because configuration admission fails before native resolution. ### Interface CLI ### Earliest failure stage Setup or provider admission ### Relevant output or evidence The relative and unset runs returned the following fields. The only omitted member here is `details.partial_observation`, which retains the fixture manifest/source locations, digests and sizes and contains no executable selection. The reproduction compares the complete JSON documents, including that member. ```json { "code": "capability_unavailable", "category": "unsupported_provider", "message": "Provide an absolute REA_BROWSER_EXECUTABLE for a caller-supplied Chromium browser supporting import.meta.resolve. REA installs no browser.", "retryable": false, "remediation": { "action": "Provide an absolute REA_BROWSER_EXECUTABLE for a caller-supplied Chromium browser supporting import.meta.resolve. REA installs no browser." }, "details": { "provider_id": "native-module-resolver", "operation": "trace_web_module_imports", "reason": "missing_browser_executable" } } ``` The absolute control instead returned `details.diagnostics.executable` equal to its selected nonexistent path, and `details.diagnostics.error_message` beginning `browserType.launch: Failed to launch chromium because executable doesn't exist at`, followed by that same path. No executable was present or created at either selection. Contract locators at the baseline: `AGENTS.md:13,19,33` require retaining caller-selected paths, omission and meaningful failure facts. `docs/website-module-trace.md:11-21` shows caller-supplied Chromium with an absolute example. `src/contracts/webScriptToolContracts.ts:47` explicitly requires absolute REA_BROWSER_EXECUTABLE for literal imports. The guide does not prescribe the exact missing-browser message or reason key. Reachability locators: `src/cli/webModuleCommands.ts:17-70`, `src/composition/webModules.ts:6-12`, and `src/application/WebModuleTraceService.ts:150-175` connect the public CLI and verified literal source to native resolution. `src/browser/modules/NativeModuleResolver.ts:116-128` handles absent and relative values together and supplies neither value nor a distinguishing fact. Its absolute-launch failure path at `:236-242` retains the executable and error message. The executed build contains the same configuration guard at `dist/cli-runtime/cli.js:62888-62889`. ### Typed outcome or recovery expected Keep the absolute-path requirement and non-retryable capability refusal. Retain the supplied relative spelling in a public diagnostic and distinguish rejected configuration from absence. The unset case should remain an absent-configuration refusal; the absolute missing-file case should continue retaining its selected executable and actual startup explanation. ### Analysis or suspected cause Source-derived explanation: a single guard combines an undefined environment value with any non-absolute string. It constructs the same capability error from a fixed reason and message, so the public projector has no rejected selection to retain. The later launcher catch explicitly attaches the executable and cause text, which explains the different absolute-control output. ### Additional context No same-defect report or fix was found in read-only searches of open and closed issues, issue comments, and open/closed/merged PRs. [Issue #7554](https://github.com/morluto/rea/issues/7554) concerns browser-scenario startup failures for `browser.executable_path`; it does not cover this environment setting or native module tracing. Its restoration requires startup-cause projection after launch admission. This trace already retains absolute startup failures, while its relative-setting refusal loses the selection earlier. The omissions in [#7595](https://github.com/morluto/rea/issues/7595) (WABT), [#7569](https://github.com/morluto/rea/issues/7569) (Wireshark), and [#7594](https://github.com/morluto/rea/issues/7594) (Frida) occur at different provider boundaries. [#2180](https://github.com/morluto/rea/issues/2180) and [merged PR #2294](https://github.com/morluto/rea/pull/2294) cover named input-contract correction metadata in other CLI commands. [#5103](https://github.com/morluto/rea/issues/5103) covers scenario JSON path normalization. None establishes a previous fix of this NativeModuleResolver configuration branch; no regression claim is made.
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#4565
No description provided.