[GH-ISSUE #6515] [Bug] retain promised BOM recovery in bundle-file errors #3909

Closed
opened 2026-10-31 00:45:08 +00:00 by bsakaguchi · 1 comment
Owner

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

Before submitting

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

Area

Evidence, artifacts, or reconstruction

Observed behavior

evidence-import, evidence-export source reads, and compare with a BOM-prefixed bundle on either side reject the file with only Evidence file is not valid JSON. Repair or recreate the file and try again. The error retains reason: "invalid-json" and the selected path, but neither its message, remediation nor details names the byte-order mark or the recovery promised by the earlier merged BOM-diagnostic repair.

Rejecting the prefixed input is intended. The defect is the missing BOM-specific recovery at the Evidence-file boundary, which that repair explicitly included. Canonical input succeeds, malformed JSON remains rejected, and removing exactly the three leading BOM bytes restores import, export and comparison.

Reproduction

Use a checkout with the repository-pinned Node.js runtime, locked dependencies and compiled CLI already prepared. No provider is required. The following self-contained probe creates synthetic files and a clean subprocess environment, runs the supported CLI, checks that input bytes remain unchanged, and removes its disposable files afterward. It exercises the same command/fixture matrix as the independent verification.

python3 - <<'PY'
import json, os, pathlib, shutil, subprocess, tempfile

repo = pathlib.Path.cwd()
node = shutil.which('node')
assert node, 'Put the repository-pinned Node.js runtime on PATH'
cli = repo / 'scripts' / 'rea.mjs'
canonical = b'{"artifacts":[],"captures":[],"environments":[],"providers":[],"records":[],"scenarios":[],"unknowns":[]}'
fixtures = {
    'canonical': canonical,
    'bom': b'\xef\xbb\xbf' + canonical,
    'invalid-utf8': canonical.replace(b'"unknowns":[]', b'"unknowns":["\xe9"]'),
    'malformed': b'{',
}
with tempfile.TemporaryDirectory(prefix='evidence-encoding-') as directory:
    work = pathlib.Path(directory)
    env = {'PATH': os.environ.get('PATH', ''), 'REA_LOG_LEVEL': 'silent', 'NO_COLOR': '1'}
    for key in ('HOME', 'XDG_CONFIG_HOME', 'XDG_DATA_HOME', 'XDG_CACHE_HOME',
                'XDG_STATE_HOME', 'TMPDIR', 'DSH_HOME', 'DSH_AGENTS_HOME'):
        folder = work / key.lower()
        folder.mkdir()
        env[key] = str(folder)
    for name, content in fixtures.items():
        (work / (name + '.json')).write_bytes(content)

    def run(label, args, expected):
        result = subprocess.run([node, str(cli), *map(str, args), '--json'],
            cwd=repo, env=env, text=True, capture_output=True, timeout=45)
        assert result.returncode == expected, (label, result.stdout, result.stderr)
        value = json.loads(result.stdout)
        print(label, 'exit=' + str(result.returncode),
              json.dumps({key: value[key] for key in
                  ('message', 'imported', 'bytes', 'status') if key in value}))
        if expected == 1:
            assert value['details']['reason'] == 'invalid-json'
            assert value['message'] == 'Evidence file is not valid JSON. Repair or recreate the file and try again.'
            assert 'byte-order mark' not in json.dumps(value)
        return value

    good = work / 'canonical.json'
    for name, content in fixtures.items():
        source = work / (name + '.json')
        output = work / (name + '-out.json')
        expected = 0 if name == 'canonical' else 1
        run(name + '-import', ['evidence-import', source], expected)
        run(name + '-export', ['evidence-export', source, output], expected)
        run(name + '-compare-left', ['compare', source, good], expected)
        run(name + '-compare-right', ['compare', good, source], expected)
        assert source.read_bytes() == content
        assert output.exists() == (expected == 0)
    exported = work / 'canonical-out.json'
    assert exported.read_bytes() == canonical
    run('generated-export-import', ['evidence-import', exported], 0)
    run('generated-export-compare', ['compare', exported, good], 0)
    recovered = work / 'recovered.json'
    recovered.write_bytes((work / 'bom.json').read_bytes()[3:])
    assert recovered.read_bytes() == canonical
    run('recovered-import', ['evidence-import', recovered], 0)
    run('recovered-export', ['evidence-export', recovered, work / 'recovered-out.json'], 0)
    run('recovered-compare', ['compare', recovered, good], 0)
PY

Each independent replay ran four BOM rejections, four invalid-UTF-8 refusals, four malformed-syntax refusals, four canonical successes, two generated-export successes and three recovered-file successes. The matrix ran twice: first as the standalone verification probe, then by extracting and executing this report's exact inline Python block. A separate version call reported the package version. Both replays passed all preservation and output-existence assertions.

Expected behavior

Keep rejecting a leading BOM as strict JSON input, while making the BOM cause and the recovery visible in the returned failure: save the file as UTF-8 without the initial byte-order mark. The merged BOM-diagnostic repair explicitly included Evidence-bundle readers, and the shared reader still constructs that explanation. The requirement concerns observable recovery guidance; it does not require an exact sentence, a particular field, BOM acceptance or a different error taxonomy.

Malformed JSON must remain rejected with its existing generic diagnostic, and canonical bundles must continue to import, export and compare successfully. Invalid UTF-8 is included as a refusal control; this report does not establish a separate promise of encoding-specific recovery for that case.

Impact and scope

The observed effect is missing diagnostic and recovery wording on all three local bundle commands. The rejection remains safe and accurate as a strict-JSON refusal. No input modification, data loss, altered accepted Evidence or incorrect comparison was observed. Failed exports created no destination.

A safe recovery was demonstrated: remove exactly the initial EF BB BF, retaining every following byte. The resulting file matched the canonical input exactly and imported, exported and compared successfully. Population frequency was not measured. The reported scope is CLI file input; no real MCP transport was executed.

REA version

rea-agents 6.7.0, source revision b395190e4273b2542a599aa2cdce61107f3a7ebc.

Runtime and platform

Node.js v24.18.0; macOS 27.0.1, build 26A434; arm64. The existing compiled CLI was used without installing dependencies or rebuilding. No analysis engine was configured or started by the probe.

Target and artifact context

A 105-byte canonical empty Evidence bundle; the same content with the three-byte UTF-8 BOM prepended; malformed JSON containing only {; and a separate invalid-UTF-8 control containing byte E9 in a JSON string. No binary target, external service or session state is needed.

Interface

CLI

Earliest failure stage

Request parsing or validation

Relevant output or evidence

  • Expected source: Merged BOM-diagnostic repair #1510, Change section, explicitly includes Evidence-bundle and snapshot readers through JsonFiles and says the diagnostic names the mark and recovery. The baseline includes that repair. src/application/Utf8JsonInput.ts:10-15 retains the corresponding strict-rejection explanation. docs/cli.md:319-336 documents the tested bundle-file operations.
  • Failure source: Independent compiled-CLI calls returned the complete generic envelope below for each BOM case. src/application/EvidenceBundleFiles.ts:61-80 reaches the JSON file reader; src/application/JsonFiles.ts:139-151 translates its input failure while retaining the specific explanation only through cause; src/domain/analysisErrorPresentation.ts:502-503 selects the generic wording; src/domain/analysisErrorProjection.ts:387-409 does not project that cause.
  • Evidence provenance: observed CLI output and file-preservation/recovery assertions; source inspection corroborates the translation path. The merged repair supplies the expected behavior, rather than the hunter's unverified draft.
  • Local verification: reproduced.
  • Reproduction completeness: complete.

Canonical import returned exit 0 and {"imported":0,"unknowns_added":0,"total":0}. Canonical export returned exit 0 with 105 bytes, and the output bytes equaled the input. Canonical and recovered comparisons returned status: "unchanged" and equal bundle hashes.

Every BOM read returned exit 1 and this complete JSON envelope, with only its disposable path normalized to $WORK/bom.json:

{"error":"Analysis failed","code":"evidence_integrity_mismatch","category":"execution_failure","message":"Evidence file is not valid JSON. Repair or recreate the file and try again.","retryable":false,"remediation":{"action":"Evidence file is not valid JSON. Repair or recreate the file and try again."},"details":{"operation":"read","reason":"invalid-json","path":"$WORK/bom.json"}}

Restoration check: repeat the BOM import, export-source read and both compare positions. Each must still reject the input, with the initial mark and UTF-8-without-BOM recovery visible in the returned message or structured diagnostic. Preserve canonical/generated-export/recovered-file success, unchanged input bytes, no output from rejected exports, and malformed-JSON refusal. The invalid-UTF-8 control must remain rejected; a new specific message for that control is outside this report's acceptance criteria.

Typed outcome or recovery expected

Retain the typed refusal and selected path while exposing the BOM-specific cause and recovery to the caller. Preserve existing file-integrity validation and accepted input semantics.

Analysis or suspected cause

Source-derived: the reader's BOM explanation survives only as an internal cause after conversion to EvidenceFileError. The CLI's public projection emits a fixed message and does not include the cause. This explains the observed output without requiring a particular repair design. No last-known-good Evidence CLI behavior was established, so this is an incomplete earlier repair rather than a demonstrated regression.

Additional context

Open and closed issue searches and related repair inspection found no existing issue with this exact remaining Evidence BOM-diagnostic failure. The earlier merged BOM repair is directly relevant but its public Evidence output remains incomplete. HAR BOM acceptance is a separate format contract. Evidence file-selection repairs deliberately retain invalid-json, and the non-object-snapshot repair uses the generic diagnostic; this report does not challenge those decisions. Likewise, the large-JSON resource-limit repairs address a different trigger and recovery.

Triage assessment

  • Impact level: P3
  • Assessment status: supported
  • Impact basis: the supported failure is missing promised recovery wording for rejected BOM input; no consequential accepted-data or analysis behavior change was observed.
  • Workaround status: available
  • Workaround basis: removing exactly the initial three BOM bytes preserved the complete bundle content and restored import, export and comparison in the executed recovery controls.
Originally created by @coygeek on GitHub (Oct 23, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6515 ## 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 Evidence, artifacts, or reconstruction ## Observed behavior `evidence-import`, `evidence-export` source reads, and `compare` with a BOM-prefixed bundle on either side reject the file with only `Evidence file is not valid JSON. Repair or recreate the file and try again.` The error retains `reason: "invalid-json"` and the selected path, but neither its message, remediation nor details names the byte-order mark or the recovery promised by the earlier merged BOM-diagnostic repair. Rejecting the prefixed input is intended. The defect is the missing BOM-specific recovery at the Evidence-file boundary, which that repair explicitly included. Canonical input succeeds, malformed JSON remains rejected, and removing exactly the three leading BOM bytes restores import, export and comparison. ## Reproduction Use a checkout with the repository-pinned Node.js runtime, locked dependencies and compiled CLI already prepared. No provider is required. The following self-contained probe creates synthetic files and a clean subprocess environment, runs the supported CLI, checks that input bytes remain unchanged, and removes its disposable files afterward. It exercises the same command/fixture matrix as the independent verification. ```sh python3 - <<'PY' import json, os, pathlib, shutil, subprocess, tempfile repo = pathlib.Path.cwd() node = shutil.which('node') assert node, 'Put the repository-pinned Node.js runtime on PATH' cli = repo / 'scripts' / 'rea.mjs' canonical = b'{"artifacts":[],"captures":[],"environments":[],"providers":[],"records":[],"scenarios":[],"unknowns":[]}' fixtures = { 'canonical': canonical, 'bom': b'\xef\xbb\xbf' + canonical, 'invalid-utf8': canonical.replace(b'"unknowns":[]', b'"unknowns":["\xe9"]'), 'malformed': b'{', } with tempfile.TemporaryDirectory(prefix='evidence-encoding-') as directory: work = pathlib.Path(directory) env = {'PATH': os.environ.get('PATH', ''), 'REA_LOG_LEVEL': 'silent', 'NO_COLOR': '1'} for key in ('HOME', 'XDG_CONFIG_HOME', 'XDG_DATA_HOME', 'XDG_CACHE_HOME', 'XDG_STATE_HOME', 'TMPDIR', 'DSH_HOME', 'DSH_AGENTS_HOME'): folder = work / key.lower() folder.mkdir() env[key] = str(folder) for name, content in fixtures.items(): (work / (name + '.json')).write_bytes(content) def run(label, args, expected): result = subprocess.run([node, str(cli), *map(str, args), '--json'], cwd=repo, env=env, text=True, capture_output=True, timeout=45) assert result.returncode == expected, (label, result.stdout, result.stderr) value = json.loads(result.stdout) print(label, 'exit=' + str(result.returncode), json.dumps({key: value[key] for key in ('message', 'imported', 'bytes', 'status') if key in value})) if expected == 1: assert value['details']['reason'] == 'invalid-json' assert value['message'] == 'Evidence file is not valid JSON. Repair or recreate the file and try again.' assert 'byte-order mark' not in json.dumps(value) return value good = work / 'canonical.json' for name, content in fixtures.items(): source = work / (name + '.json') output = work / (name + '-out.json') expected = 0 if name == 'canonical' else 1 run(name + '-import', ['evidence-import', source], expected) run(name + '-export', ['evidence-export', source, output], expected) run(name + '-compare-left', ['compare', source, good], expected) run(name + '-compare-right', ['compare', good, source], expected) assert source.read_bytes() == content assert output.exists() == (expected == 0) exported = work / 'canonical-out.json' assert exported.read_bytes() == canonical run('generated-export-import', ['evidence-import', exported], 0) run('generated-export-compare', ['compare', exported, good], 0) recovered = work / 'recovered.json' recovered.write_bytes((work / 'bom.json').read_bytes()[3:]) assert recovered.read_bytes() == canonical run('recovered-import', ['evidence-import', recovered], 0) run('recovered-export', ['evidence-export', recovered, work / 'recovered-out.json'], 0) run('recovered-compare', ['compare', recovered, good], 0) PY ``` Each independent replay ran four BOM rejections, four invalid-UTF-8 refusals, four malformed-syntax refusals, four canonical successes, two generated-export successes and three recovered-file successes. The matrix ran twice: first as the standalone verification probe, then by extracting and executing this report's exact inline Python block. A separate version call reported the package version. Both replays passed all preservation and output-existence assertions. ## Expected behavior Keep rejecting a leading BOM as strict JSON input, while making the BOM cause and the recovery visible in the returned failure: save the file as UTF-8 without the initial byte-order mark. The merged BOM-diagnostic repair explicitly included Evidence-bundle readers, and the shared reader still constructs that explanation. The requirement concerns observable recovery guidance; it does not require an exact sentence, a particular field, BOM acceptance or a different error taxonomy. Malformed JSON must remain rejected with its existing generic diagnostic, and canonical bundles must continue to import, export and compare successfully. Invalid UTF-8 is included as a refusal control; this report does not establish a separate promise of encoding-specific recovery for that case. ## Impact and scope The observed effect is missing diagnostic and recovery wording on all three local bundle commands. The rejection remains safe and accurate as a strict-JSON refusal. No input modification, data loss, altered accepted Evidence or incorrect comparison was observed. Failed exports created no destination. A safe recovery was demonstrated: remove exactly the initial `EF BB BF`, retaining every following byte. The resulting file matched the canonical input exactly and imported, exported and compared successfully. Population frequency was not measured. The reported scope is CLI file input; no real MCP transport was executed. ## REA version `rea-agents` 6.7.0, source revision `b395190e4273b2542a599aa2cdce61107f3a7ebc`. ## Runtime and platform Node.js v24.18.0; macOS 27.0.1, build 26A434; arm64. The existing compiled CLI was used without installing dependencies or rebuilding. No analysis engine was configured or started by the probe. ## Target and artifact context A 105-byte canonical empty Evidence bundle; the same content with the three-byte UTF-8 BOM prepended; malformed JSON containing only `{`; and a separate invalid-UTF-8 control containing byte `E9` in a JSON string. No binary target, external service or session state is needed. ## Interface CLI ## Earliest failure stage Request parsing or validation ## Relevant output or evidence - Expected source: [Merged BOM-diagnostic repair #1510](https://github.com/morluto/rea/pull/1510), Change section, explicitly includes Evidence-bundle and snapshot readers through `JsonFiles` and says the diagnostic names the mark and recovery. The baseline includes that repair. `src/application/Utf8JsonInput.ts:10-15` retains the corresponding strict-rejection explanation. `docs/cli.md:319-336` documents the tested bundle-file operations. - Failure source: Independent compiled-CLI calls returned the complete generic envelope below for each BOM case. `src/application/EvidenceBundleFiles.ts:61-80` reaches the JSON file reader; `src/application/JsonFiles.ts:139-151` translates its input failure while retaining the specific explanation only through `cause`; `src/domain/analysisErrorPresentation.ts:502-503` selects the generic wording; `src/domain/analysisErrorProjection.ts:387-409` does not project that cause. - Evidence provenance: observed CLI output and file-preservation/recovery assertions; source inspection corroborates the translation path. The merged repair supplies the expected behavior, rather than the hunter's unverified draft. - Local verification: reproduced. - Reproduction completeness: complete. Canonical import returned exit 0 and `{"imported":0,"unknowns_added":0,"total":0}`. Canonical export returned exit 0 with 105 bytes, and the output bytes equaled the input. Canonical and recovered comparisons returned `status: "unchanged"` and equal bundle hashes. Every BOM read returned exit 1 and this complete JSON envelope, with only its disposable path normalized to `$WORK/bom.json`: ```json {"error":"Analysis failed","code":"evidence_integrity_mismatch","category":"execution_failure","message":"Evidence file is not valid JSON. Repair or recreate the file and try again.","retryable":false,"remediation":{"action":"Evidence file is not valid JSON. Repair or recreate the file and try again."},"details":{"operation":"read","reason":"invalid-json","path":"$WORK/bom.json"}} ``` Restoration check: repeat the BOM import, export-source read and both compare positions. Each must still reject the input, with the initial mark and UTF-8-without-BOM recovery visible in the returned message or structured diagnostic. Preserve canonical/generated-export/recovered-file success, unchanged input bytes, no output from rejected exports, and malformed-JSON refusal. The invalid-UTF-8 control must remain rejected; a new specific message for that control is outside this report's acceptance criteria. ## Typed outcome or recovery expected Retain the typed refusal and selected path while exposing the BOM-specific cause and recovery to the caller. Preserve existing file-integrity validation and accepted input semantics. ## Analysis or suspected cause Source-derived: the reader's BOM explanation survives only as an internal cause after conversion to `EvidenceFileError`. The CLI's public projection emits a fixed message and does not include the cause. This explains the observed output without requiring a particular repair design. No last-known-good Evidence CLI behavior was established, so this is an incomplete earlier repair rather than a demonstrated regression. ## Additional context Open and closed issue searches and related repair inspection found no existing issue with this exact remaining Evidence BOM-diagnostic failure. The earlier merged BOM repair is directly relevant but its public Evidence output remains incomplete. HAR BOM acceptance is a separate format contract. Evidence file-selection repairs deliberately retain `invalid-json`, and the non-object-snapshot repair uses the generic diagnostic; this report does not challenge those decisions. Likewise, the large-JSON resource-limit repairs address a different trigger and recovery. ## Triage assessment - Impact level: P3 - Assessment status: supported - Impact basis: the supported failure is missing promised recovery wording for rejected BOM input; no consequential accepted-data or analysis behavior change was observed. - Workaround status: available - Workaround basis: removing exactly the initial three BOM bytes preserved the complete bundle content and restored import, export and comparison in the executed recovery controls.
Author
Owner

@coygeek commented on GitHub (Oct 24, 2026):

At d87e73f2db50be72d33a4b3c7199af35518b441c, the JSON-file boundary retains the format owner’s BOM recovery and content issues (translation); public error presentation exposes that retained message. The CLI regression covers import, export-source reads and both comparison positions, requiring BOM-specific recovery while preserving input bytes and refusing export publication. Reader coverage also distinguishes malformed JSON.

I ran the cited regression files on this revision; all passed as part of the focused 28-file, 603-test run. Provider-facing checks here use controlled fixtures; this does not claim a run with the real analysis engine. Closing as completed on main.

<!-- gh-comment-id:6380641074 --> @coygeek commented on GitHub (Oct 24, 2026): At `d87e73f2db50be72d33a4b3c7199af35518b441c`, the JSON-file boundary retains the format owner’s BOM recovery and content issues ([translation](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/application/JsonFiles.ts#L180)); public error presentation exposes that retained message. The [CLI regression](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/tests/boundary/cli/evidenceBundleCommands.test.ts#L59) covers import, export-source reads and both comparison positions, requiring BOM-specific recovery while preserving input bytes and refusing export publication. [Reader coverage](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/tests/boundary/filesystem/evidenceBundleFiles.test.ts#L52) also distinguishes malformed JSON. I ran the cited regression files on this revision; all passed as part of the focused 28-file, 603-test run. Provider-facing checks here use controlled fixtures; this does not claim a run with the real analysis engine. Closing as completed on main.
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#3909
No description provided.