[GH-ISSUE #7443] [Bug] evidence-import, evidence-export and compare diagnose an empty argument as a non-file working directory #4447

Closed
opened 2026-10-31 00:49:53 +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/7443

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

CLI

Observed behavior

Passing an empty string in any of the five local path positions of evidence-import, evidence-export and compare returns exit 1, invalid_request and “Evidence path does not point to a regular file. Choose a file and try again.” The error names the current working directory with a trailing slash and reason: "not-file"; it does not identify the empty argument. This affects the import input, export source and destination, and either comparison input. The operations refuse the input, but report a different selection and cause from the argument supplied.

Reproduction

  1. From the baseline checkout with its locked dependencies and provided built runtime available, select Node 24.22.1 and run this block. It creates an empty temporary working directory, redirects HOME, authors the bundle inline and removes the fixture afterward. All arguments supplied to the CLI are empty strings or relative file names.
python3 - <<'PYTHON'
import json, os, pathlib, subprocess, tempfile

cli = pathlib.Path('scripts/rea.mjs').resolve()
canonical = b'{"artifacts":[],"captures":[],"environments":[],"providers":[],"records":[],"scenarios":[],"unknowns":[]}'
with tempfile.TemporaryDirectory(prefix='rea-empty-evidence-') as directory:
    work = pathlib.Path(directory).resolve()
    (work / 'home').mkdir()
    (work / 'bundle.json').write_bytes(canonical)
    env = {'PATH': os.environ['PATH'], 'HOME': str(work / 'home'), 'REA_LOG_LEVEL': 'silent'}
    cases = [
        ('import-input', ['evidence-import', '']),
        ('export-source', ['evidence-export', '', 'refused.json']),
        ('export-output', ['evidence-export', 'bundle.json', '']),
        ('compare-left', ['compare', '', 'bundle.json']),
        ('compare-right', ['compare', 'bundle.json', '']),
    ]
    def invoke(arguments):
        result = subprocess.run(['node', str(cli), *arguments, '--json'], cwd=work, env=env, capture_output=True, text=True, timeout=30)
        assert result.stderr == '', result.stderr
        return result.returncode, json.loads(result.stdout)
    for label, arguments in cases:
        status, data = invoke(arguments)
        assert status == 1 and data['code'] == 'invalid_request'
        assert data['message'] == 'Evidence path does not point to a regular file. Choose a file and try again.'
        assert data['details']['path'] == str(work) + '/'
        assert data['details']['reason'] == 'not-file'
        print(json.dumps({'case': label, 'exit': status, 'code': data['code'], 'operation': data['details']['operation'], 'reason': data['details']['reason'], 'path': '<work>/'}, separators=(',', ':')))
    for label, arguments in [
        ('valid-import', ['evidence-import', 'bundle.json']),
        ('valid-export', ['evidence-export', 'bundle.json', 'canonical.json']),
        ('valid-compare', ['compare', 'bundle.json', 'bundle.json']),
    ]:
        status, data = invoke(arguments)
        assert status == 0, data
        if label == 'valid-import': assert data['imported'] == 0
        if label == 'valid-compare': assert data['status'] == 'unchanged'
        print(json.dumps({'case': label, 'exit': status}, separators=(',', ':')))
    status, data = invoke(['inspect-artifact', ''])
    assert status == 1 and data['code'] == 'VALIDATION_ERROR'
    field = data['fieldErrors'][0]
    assert field['path'] == 'path' and field['code'] == 'too_small'
    print(json.dumps({'case': 'empty-control', 'exit': status, 'code': data['code'], 'field': field['path'], 'issue': field['code']}, separators=(',', ':')))
    assert (work / 'bundle.json').read_bytes() == canonical
    assert (work / 'canonical.json').read_bytes() == canonical
    assert not (work / 'refused.json').exists()
    assert sorted(p.name for p in work.iterdir()) == ['bundle.json', 'canonical.json', 'home']
    print('input unchanged; valid export matches; rejected export absent')
PYTHON
  1. Observe the exact projected output below. <work>/ replaces the temporary working-directory path only after the script checks the complete returned value. The full errors also carry the regular-file message asserted above.
{"case":"import-input","exit":1,"code":"invalid_request","operation":"read","reason":"not-file","path":"<work>/"}
{"case":"export-source","exit":1,"code":"invalid_request","operation":"read","reason":"not-file","path":"<work>/"}
{"case":"export-output","exit":1,"code":"invalid_request","operation":"write","reason":"not-file","path":"<work>/"}
{"case":"compare-left","exit":1,"code":"invalid_request","operation":"read","reason":"not-file","path":"<work>/"}
{"case":"compare-right","exit":1,"code":"invalid_request","operation":"read","reason":"not-file","path":"<work>/"}
{"case":"valid-import","exit":0}
{"case":"valid-export","exit":0}
{"case":"valid-compare","exit":0}
{"case":"empty-control","exit":1,"code":"VALIDATION_ERROR","field":"path","issue":"too_small"}
input unchanged; valid export matches; rejected export absent

The independent initial matrix reproduced each empty position in two attempts. The exact reduced block above reproduced each once more, giving three failures in three attempts per position. Valid-path controls and the field-specific empty inspect-artifact rejection succeeded in every matrix.

Expected behavior

Refuse the empty argument as an empty local-path selection before relative path resolution, with enough information to identify and correct that argument. The “Run a command” section of docs/cli.md, lines 42–45, says: “Local file inspection commands reject empty paths and NUL bytes before resolving relative inputs, auxiliary files or output destinations.” The same paragraph states that an empty string never selects the working directory. The “Import, export and compare Evidence” section documents these local bundle-validation, export and comparison workflows. The requirement applies to the stage of refusal as well as to successful inspections; an eventual directory refusal after substitution does not preserve it. No specific replacement error code or envelope is required.

Impact and scope

The observed impact is misleading error wording and a working-directory path in place of an empty selection. Every tested empty argument was refused; no wrong-target Evidence or unintended file publication occurred. The input bundle remained unchanged, valid export bytes matched the input, and the rejected source export created no destination. Empty export output was tested with an explicitly selected valid source, which is read before the destination refusal. Other platforms, MCP runtime behavior and downstream handling of these diagnostics were not tested. Callers can detect an empty variable before invocation; valid relative file arguments worked in the controls.

REA version

6.8.0 (source commit a905a71e31)

Runtime and platform

Node 24.22.1; macOS 27.0.1 (build 26A434), arm64.

Target and artifact context

Inline canonical Evidence bundle containing seven empty arrays; isolated local working directory and HOME; no executable target or external analysis provider.

Interface

CLI

Earliest failure stage

Request parsing or validation

Relevant output or evidence

Observed: the public dispatcher returned the five refusal shapes and controls shown above. The reduced replay asserts each raw reported path equals cwd plus a separator before substituting the display marker. It also verifies preservation of the input, exact valid-export bytes and absence of rejected output.

Source-only (not executed as a separate source test): src/cliEvidenceCommands.ts:27 declares the import path as a plain string; lines 38–39 do the same for both export paths and lines 57–58 for comparison. Those handlers reach the shared Evidence bundle readers/writer. src/application/JsonFiles.ts:137 resolves the read selection before regular-file admission, and line 350 resolves the output selection before the directory-suffix refusal at lines 353–355. src/domain/evidenceErrors.ts:57 describes its path as the caller-selected path resolved against cwd. These locators support the discrepancy between the original argument and the projected file-selection diagnosis.

Typed outcome or recovery expected

A nonzero validation result identifying the empty argument before it becomes cwd. Preserve successful nonempty bundle inputs and exports. The existing empty inspect-artifact control demonstrates a field-specific VALIDATION_ERROR, but that exact shape is an example rather than a required repair design.

Analysis or suspected cause

Source-derived: the Evidence CLI schemas admit empty strings, while src/filesystem/callerPath.ts:35-45 anchors relative strings to cwd. The resulting path reaches regular-file validation and produces the accurately classified directory failure for the substituted value, losing the original empty-selection diagnosis. The nonempty local-path constraint already exists at src/domain/localPath.ts:20. This finding does not require changing the shared helper's behavior for other callers.

Additional context

Open reports #7280, #7377, #7404 and #7406 cover other commands returning successful analysis or output after empty-target substitution. #7421 covers dispatch metadata and Apple IPS refusals after substitution. Their command-specific acceptance scopes omit these Evidence registrations and their five file-argument positions. Open PR #7284 changes only decompile-all. Merged PR #2687 supplies an earlier refusal-only precedent for analyze/inspect-artifact, whose error named an unintended directory before its argument-boundary correction.

Related (closed):
Issue #6757 addressed desktop inspection; its command-specific correction leaves the separate Evidence declarations accepting empty strings, and the diagnostic defect still reproduced at this baseline.

The npm registry reported 6.8.0 as the latest published rea-agents version during verification. The provided built dispatcher was exercised without rebuilding or modifying the checkout.

Related (open): the same empty-target behavior is reported for other command families in #7280, #7377, #7404, #7406, #7421 and #7436; none of them covers evidence-import, evidence-export or compare, whose failing signal is the diagnostic naming the working directory instead of the empty argument.

Originally created by @coygeek on GitHub (Oct 27, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/7443 ### 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 CLI ### Observed behavior Passing an empty string in any of the five local path positions of `evidence-import`, `evidence-export` and `compare` returns exit 1, `invalid_request` and “Evidence path does not point to a regular file. Choose a file and try again.” The error names the current working directory with a trailing slash and `reason: "not-file"`; it does not identify the empty argument. This affects the import input, export source and destination, and either comparison input. The operations refuse the input, but report a different selection and cause from the argument supplied. ### Reproduction 1. From the baseline checkout with its locked dependencies and provided built runtime available, select Node 24.22.1 and run this block. It creates an empty temporary working directory, redirects HOME, authors the bundle inline and removes the fixture afterward. All arguments supplied to the CLI are empty strings or relative file names. ```sh python3 - <<'PYTHON' import json, os, pathlib, subprocess, tempfile cli = pathlib.Path('scripts/rea.mjs').resolve() canonical = b'{"artifacts":[],"captures":[],"environments":[],"providers":[],"records":[],"scenarios":[],"unknowns":[]}' with tempfile.TemporaryDirectory(prefix='rea-empty-evidence-') as directory: work = pathlib.Path(directory).resolve() (work / 'home').mkdir() (work / 'bundle.json').write_bytes(canonical) env = {'PATH': os.environ['PATH'], 'HOME': str(work / 'home'), 'REA_LOG_LEVEL': 'silent'} cases = [ ('import-input', ['evidence-import', '']), ('export-source', ['evidence-export', '', 'refused.json']), ('export-output', ['evidence-export', 'bundle.json', '']), ('compare-left', ['compare', '', 'bundle.json']), ('compare-right', ['compare', 'bundle.json', '']), ] def invoke(arguments): result = subprocess.run(['node', str(cli), *arguments, '--json'], cwd=work, env=env, capture_output=True, text=True, timeout=30) assert result.stderr == '', result.stderr return result.returncode, json.loads(result.stdout) for label, arguments in cases: status, data = invoke(arguments) assert status == 1 and data['code'] == 'invalid_request' assert data['message'] == 'Evidence path does not point to a regular file. Choose a file and try again.' assert data['details']['path'] == str(work) + '/' assert data['details']['reason'] == 'not-file' print(json.dumps({'case': label, 'exit': status, 'code': data['code'], 'operation': data['details']['operation'], 'reason': data['details']['reason'], 'path': '<work>/'}, separators=(',', ':'))) for label, arguments in [ ('valid-import', ['evidence-import', 'bundle.json']), ('valid-export', ['evidence-export', 'bundle.json', 'canonical.json']), ('valid-compare', ['compare', 'bundle.json', 'bundle.json']), ]: status, data = invoke(arguments) assert status == 0, data if label == 'valid-import': assert data['imported'] == 0 if label == 'valid-compare': assert data['status'] == 'unchanged' print(json.dumps({'case': label, 'exit': status}, separators=(',', ':'))) status, data = invoke(['inspect-artifact', '']) assert status == 1 and data['code'] == 'VALIDATION_ERROR' field = data['fieldErrors'][0] assert field['path'] == 'path' and field['code'] == 'too_small' print(json.dumps({'case': 'empty-control', 'exit': status, 'code': data['code'], 'field': field['path'], 'issue': field['code']}, separators=(',', ':'))) assert (work / 'bundle.json').read_bytes() == canonical assert (work / 'canonical.json').read_bytes() == canonical assert not (work / 'refused.json').exists() assert sorted(p.name for p in work.iterdir()) == ['bundle.json', 'canonical.json', 'home'] print('input unchanged; valid export matches; rejected export absent') PYTHON ``` 2. Observe the exact projected output below. `<work>/` replaces the temporary working-directory path only after the script checks the complete returned value. The full errors also carry the regular-file message asserted above. ```text {"case":"import-input","exit":1,"code":"invalid_request","operation":"read","reason":"not-file","path":"<work>/"} {"case":"export-source","exit":1,"code":"invalid_request","operation":"read","reason":"not-file","path":"<work>/"} {"case":"export-output","exit":1,"code":"invalid_request","operation":"write","reason":"not-file","path":"<work>/"} {"case":"compare-left","exit":1,"code":"invalid_request","operation":"read","reason":"not-file","path":"<work>/"} {"case":"compare-right","exit":1,"code":"invalid_request","operation":"read","reason":"not-file","path":"<work>/"} {"case":"valid-import","exit":0} {"case":"valid-export","exit":0} {"case":"valid-compare","exit":0} {"case":"empty-control","exit":1,"code":"VALIDATION_ERROR","field":"path","issue":"too_small"} input unchanged; valid export matches; rejected export absent ``` The independent initial matrix reproduced each empty position in two attempts. The exact reduced block above reproduced each once more, giving three failures in three attempts per position. Valid-path controls and the field-specific empty inspect-artifact rejection succeeded in every matrix. ### Expected behavior Refuse the empty argument as an empty local-path selection before relative path resolution, with enough information to identify and correct that argument. The “Run a command” section of `docs/cli.md`, lines 42–45, says: “Local file inspection commands reject empty paths and NUL bytes before resolving relative inputs, auxiliary files or output destinations.” The same paragraph states that an empty string never selects the working directory. The “Import, export and compare Evidence” section documents these local bundle-validation, export and comparison workflows. The requirement applies to the stage of refusal as well as to successful inspections; an eventual directory refusal after substitution does not preserve it. No specific replacement error code or envelope is required. ### Impact and scope The observed impact is misleading error wording and a working-directory path in place of an empty selection. Every tested empty argument was refused; no wrong-target Evidence or unintended file publication occurred. The input bundle remained unchanged, valid export bytes matched the input, and the rejected source export created no destination. Empty export output was tested with an explicitly selected valid source, which is read before the destination refusal. Other platforms, MCP runtime behavior and downstream handling of these diagnostics were not tested. Callers can detect an empty variable before invocation; valid relative file arguments worked in the controls. ### REA version 6.8.0 (source commit a905a71e31f71457cb21fd056b234f872126974e) ### Runtime and platform Node 24.22.1; macOS 27.0.1 (build 26A434), arm64. ### Target and artifact context Inline canonical Evidence bundle containing seven empty arrays; isolated local working directory and HOME; no executable target or external analysis provider. ### Interface CLI ### Earliest failure stage Request parsing or validation ### Relevant output or evidence Observed: the public dispatcher returned the five refusal shapes and controls shown above. The reduced replay asserts each raw reported path equals cwd plus a separator before substituting the display marker. It also verifies preservation of the input, exact valid-export bytes and absence of rejected output. Source-only (not executed as a separate source test): `src/cliEvidenceCommands.ts:27` declares the import path as a plain string; lines 38–39 do the same for both export paths and lines 57–58 for comparison. Those handlers reach the shared Evidence bundle readers/writer. `src/application/JsonFiles.ts:137` resolves the read selection before regular-file admission, and line 350 resolves the output selection before the directory-suffix refusal at lines 353–355. `src/domain/evidenceErrors.ts:57` describes its path as the caller-selected path resolved against cwd. These locators support the discrepancy between the original argument and the projected file-selection diagnosis. ### Typed outcome or recovery expected A nonzero validation result identifying the empty argument before it becomes cwd. Preserve successful nonempty bundle inputs and exports. The existing empty inspect-artifact control demonstrates a field-specific `VALIDATION_ERROR`, but that exact shape is an example rather than a required repair design. ### Analysis or suspected cause Source-derived: the Evidence CLI schemas admit empty strings, while `src/filesystem/callerPath.ts:35-45` anchors relative strings to cwd. The resulting path reaches regular-file validation and produces the accurately classified directory failure for the substituted value, losing the original empty-selection diagnosis. The nonempty local-path constraint already exists at `src/domain/localPath.ts:20`. This finding does not require changing the shared helper's behavior for other callers. ### Additional context Open reports [#7280](https://github.com/morluto/rea/issues/7280), [#7377](https://github.com/morluto/rea/issues/7377), [#7404](https://github.com/morluto/rea/issues/7404) and [#7406](https://github.com/morluto/rea/issues/7406) cover other commands returning successful analysis or output after empty-target substitution. [#7421](https://github.com/morluto/rea/issues/7421) covers dispatch metadata and Apple IPS refusals after substitution. Their command-specific acceptance scopes omit these Evidence registrations and their five file-argument positions. Open [PR #7284](https://github.com/morluto/rea/pull/7284) changes only decompile-all. Merged [PR #2687](https://github.com/morluto/rea/pull/2687) supplies an earlier refusal-only precedent for analyze/inspect-artifact, whose error named an unintended directory before its argument-boundary correction. Related (closed): [Issue #6757](https://github.com/morluto/rea/issues/6757) addressed desktop inspection; its command-specific correction leaves the separate Evidence declarations accepting empty strings, and the diagnostic defect still reproduced at this baseline. The npm registry reported 6.8.0 as the latest published rea-agents version during verification. The provided built dispatcher was exercised without rebuilding or modifying the checkout. Related (open): the same empty-target behavior is reported for other command families in [#7280](https://github.com/morluto/rea/issues/7280), [#7377](https://github.com/morluto/rea/issues/7377), [#7404](https://github.com/morluto/rea/issues/7404), [#7406](https://github.com/morluto/rea/issues/7406), [#7421](https://github.com/morluto/rea/issues/7421) and [#7436](https://github.com/morluto/rea/issues/7436); none of them covers evidence-import, evidence-export or compare, whose failing signal is the diagnostic naming the working directory instead of the empty argument.
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#4447
No description provided.