[GH-ISSUE #3387] [Bug] Empty module specifier aborts the entire historical-source inventory #1254

Closed
opened 2026-10-16 21:03:23 +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/3387

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

import-reference-source fails the entire source-tree import when a readable JavaScript file contains import "";. The command exits with status 1 and returns no file hashes, entries, manifest, or per-file diagnostic, including for an unrelated readable text file. There is no HistoricalSourceGraph or HistoricalSourceSummary.

Replacing only the JavaScript file with a malformed declaration makes the same import exit with status 0. That control retains main.mjs and other.txt as hashed entries and reports one Babel parse failure, Unexpected token (1:6). Both cases use the same readable directory and unrelated text file. The failed import's advice to check readability does not explain or recover the empty-specifier failure.

Reproduction

  1. Start from a clean checkout of https://github.com/morluto/rea at 8dde6370d0809ad04cda3342d4e6f290f48aa063. From its root, install the locked dependencies and build the runtime:
npm ci
npm run build:cached

These are clean-checkout prerequisites. The reported reproduction used an already installed and built runtime.

  1. Create disposable inputs and an isolated configuration directory, then run the import from the repository root:
case_dir="$(mktemp -d)"
mkdir -p "$case_dir/source" "$case_dir/home"
printf 'import "";\n' > "$case_dir/source/main.mjs"
printf 'retained source\n' > "$case_dir/source/other.txt"
env HOME="$case_dir/home" node scripts/rea.mjs import-reference-source "$case_dir/source" --format json > "$case_dir/result.json"
printf 'exit=%s\n' "$?"
cat "$case_dir/result.json"
  1. Observe exit status 1 and the relationships[0].to nonempty-string validation failure shown below, with no inventory.

  2. For a discriminating control, replace only the JavaScript file with a malformed declaration and repeat the same import:

printf 'const = ;\n' > "$case_dir/source/main.mjs"
env HOME="$case_dir/home" node scripts/rea.mjs import-reference-source "$case_dir/source" --format json > "$case_dir/control.json"
printf 'exit=%s\n' "$?"
cat "$case_dir/control.json"
rm -rf "$case_dir"
  1. Observe exit status 0, retained hashed entries for both files, and the per-file parse diagnostic Unexpected token (1:6).

Expected behavior

Return the complete available source inventory, including the original hashes and sizes of both files. Keep an unsupported or unresolved import target in a diagnostic or an explicit unknown state rather than discarding the entire inventory. Static source import does not require the supplied application to run successfully; the malformed-declaration control demonstrates that a source-analysis problem normally remains local to its file.

docs/cli.md:231-238 promises the complete historical graph in full mode. docs/cli.md:252-254 retains individual hashes and parse-failure diagnostics. tests/boundary/filesystem/referenceSourceImport.test.ts:401-423 imports a tree containing malformed source and retains its per-file parse failure. AGENTS.md:29 requires preserving partial facts and meaningful failure reasons.

Impact and scope

P2, supported by the reproduced loss of the entire optional historical-source inventory, including unrelated hashes and metadata, when one readable file contains an empty import target. The failure is bounded to this source construct; no source file is modified.

Workaround status is unverified. The malformed-source control verifies normal per-file recovery, but no workaround preserving the exact empty-specifier source bytes and the complete inventory was verified. Editing or excluding the affected file changes the requested historical inventory.

REA version

6.4.0 (main at 8dde6370d0)

Runtime and platform

Node.js v26.10.0; macOS 27.0.1; arm64. REA CLI 6.4.0 from the built source checkout. No external analysis engine was used.

Target and artifact context

A readable local source directory containing main.mjs with import ""; followed by a newline and other.txt with retained source followed by a newline. The control replaces only main.mjs with const = ; followed by a newline. Both invocations use the same source directory and isolated HOME.

Interface

CLI

Earliest failure stage

Artifact or evidence registration

Relevant output or evidence

The empty-specifier import exits with status 1 and returns:

{
  "error": "Import failed",
  "category": "execution_failure",
  "message": "[\n  {\n    \"origin\": \"string\",\n    \"code\": \"too_small\",\n    \"minimum\": 1,\n    \"inclusive\": true,\n    \"path\": [\n      \"relationships\",\n      0,\n      \"to\"\n    ],\n    \"message\": \"Too small: expected string to have >=1 characters\"\n  }\n] Reference source could not be indexed. Check that the source tree is readable, then try again."
}

The malformed-declaration control exits with status 0, retains both files as hashed entries, and reports one Babel parse failure, Unexpected token (1:6).

Typed outcome or recovery expected

Repeat both inputs. The empty-specifier case must return an inventory containing the exact original hashes and sizes of both files, with any import uncertainty explicit. The malformed-declaration control must continue to retain both files and its parse diagnostic. Success must preserve the source file and its original bytes, and source-analysis problems must not prevent inventory delivery.

Analysis or suspected cause

Source-derived reasoning: src/domain/referenceSourceImportParsing.ts:109-126 retains the empty string as a relationship target, while src/domain/referenceSourceGraph.ts:119-124 rejects that target during graph construction. The CLI reproduction establishes the nonempty-string validation failure and whole-inventory loss; the source inspection explains the producer/schema mismatch without establishing repaired behavior.

Additional context

  • Related closed issue #2651 also reports a whole historical-import failure, but its trigger is a retained symlink whose target is excluded from the inventory, rather than an empty module relationship target.
  • Related closed issue #1129 reports self-referential import edges rejecting JavaScript application analysis, which uses a separate graph and relationship invariant.
  • Related closed issue #2647 reports loss of an empty imported member name during successful re-export analysis; its module specifier remains nonempty and resolved.
Originally created by @coygeek on GitHub (Oct 14, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3387 ### 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 `import-reference-source` fails the entire source-tree import when a readable JavaScript file contains `import "";`. The command exits with status 1 and returns no file hashes, entries, manifest, or per-file diagnostic, including for an unrelated readable text file. There is no `HistoricalSourceGraph` or `HistoricalSourceSummary`. Replacing only the JavaScript file with a malformed declaration makes the same import exit with status 0. That control retains `main.mjs` and `other.txt` as hashed entries and reports one Babel parse failure, `Unexpected token (1:6)`. Both cases use the same readable directory and unrelated text file. The failed import's advice to check readability does not explain or recover the empty-specifier failure. ### Reproduction 1. Start from a clean checkout of `https://github.com/morluto/rea` at `8dde6370d0809ad04cda3342d4e6f290f48aa063`. From its root, install the locked dependencies and build the runtime: ```sh npm ci npm run build:cached ``` These are clean-checkout prerequisites. The reported reproduction used an already installed and built runtime. 2. Create disposable inputs and an isolated configuration directory, then run the import from the repository root: ```sh case_dir="$(mktemp -d)" mkdir -p "$case_dir/source" "$case_dir/home" printf 'import "";\n' > "$case_dir/source/main.mjs" printf 'retained source\n' > "$case_dir/source/other.txt" env HOME="$case_dir/home" node scripts/rea.mjs import-reference-source "$case_dir/source" --format json > "$case_dir/result.json" printf 'exit=%s\n' "$?" cat "$case_dir/result.json" ``` 3. Observe exit status 1 and the `relationships[0].to` nonempty-string validation failure shown below, with no inventory. 4. For a discriminating control, replace only the JavaScript file with a malformed declaration and repeat the same import: ```sh printf 'const = ;\n' > "$case_dir/source/main.mjs" env HOME="$case_dir/home" node scripts/rea.mjs import-reference-source "$case_dir/source" --format json > "$case_dir/control.json" printf 'exit=%s\n' "$?" cat "$case_dir/control.json" rm -rf "$case_dir" ``` 5. Observe exit status 0, retained hashed entries for both files, and the per-file parse diagnostic `Unexpected token (1:6)`. ### Expected behavior Return the complete available source inventory, including the original hashes and sizes of both files. Keep an unsupported or unresolved import target in a diagnostic or an explicit unknown state rather than discarding the entire inventory. Static source import does not require the supplied application to run successfully; the malformed-declaration control demonstrates that a source-analysis problem normally remains local to its file. `docs/cli.md:231-238` promises the complete historical graph in full mode. `docs/cli.md:252-254` retains individual hashes and parse-failure diagnostics. `tests/boundary/filesystem/referenceSourceImport.test.ts:401-423` imports a tree containing malformed source and retains its per-file parse failure. `AGENTS.md:29` requires preserving partial facts and meaningful failure reasons. ### Impact and scope P2, supported by the reproduced loss of the entire optional historical-source inventory, including unrelated hashes and metadata, when one readable file contains an empty import target. The failure is bounded to this source construct; no source file is modified. Workaround status is unverified. The malformed-source control verifies normal per-file recovery, but no workaround preserving the exact empty-specifier source bytes and the complete inventory was verified. Editing or excluding the affected file changes the requested historical inventory. ### REA version 6.4.0 (main at 8dde6370d0809ad04cda3342d4e6f290f48aa063) ### Runtime and platform Node.js v26.10.0; macOS 27.0.1; arm64. REA CLI 6.4.0 from the built source checkout. No external analysis engine was used. ### Target and artifact context A readable local source directory containing `main.mjs` with `import "";` followed by a newline and `other.txt` with `retained source` followed by a newline. The control replaces only `main.mjs` with `const = ;` followed by a newline. Both invocations use the same source directory and isolated HOME. ### Interface CLI ### Earliest failure stage Artifact or evidence registration ### Relevant output or evidence The empty-specifier import exits with status 1 and returns: ```text { "error": "Import failed", "category": "execution_failure", "message": "[\n {\n \"origin\": \"string\",\n \"code\": \"too_small\",\n \"minimum\": 1,\n \"inclusive\": true,\n \"path\": [\n \"relationships\",\n 0,\n \"to\"\n ],\n \"message\": \"Too small: expected string to have >=1 characters\"\n }\n] Reference source could not be indexed. Check that the source tree is readable, then try again." } ``` The malformed-declaration control exits with status 0, retains both files as hashed entries, and reports one Babel parse failure, `Unexpected token (1:6)`. ### Typed outcome or recovery expected Repeat both inputs. The empty-specifier case must return an inventory containing the exact original hashes and sizes of both files, with any import uncertainty explicit. The malformed-declaration control must continue to retain both files and its parse diagnostic. Success must preserve the source file and its original bytes, and source-analysis problems must not prevent inventory delivery. ### Analysis or suspected cause Source-derived reasoning: `src/domain/referenceSourceImportParsing.ts:109-126` retains the empty string as a relationship target, while `src/domain/referenceSourceGraph.ts:119-124` rejects that target during graph construction. The CLI reproduction establishes the nonempty-string validation failure and whole-inventory loss; the source inspection explains the producer/schema mismatch without establishing repaired behavior. ### Additional context - Related closed issue [#2651](https://github.com/morluto/rea/issues/2651) also reports a whole historical-import failure, but its trigger is a retained symlink whose target is excluded from the inventory, rather than an empty module relationship target. - Related closed issue [#1129](https://github.com/morluto/rea/issues/1129) reports self-referential import edges rejecting JavaScript application analysis, which uses a separate graph and relationship invariant. - Related closed issue [#2647](https://github.com/morluto/rea/issues/2647) reports loss of an empty imported member name during successful re-export analysis; its module specifier remains nonempty and resolved.
Author
Owner

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

Reproduced on current main (322506d) — the asymmetry holds exactly as described:

Repro (via scripts/rea.mjs import-reference-source <root> --format json):

  • main.mjs containing import ""; + unrelated other.txt → exit 1, {"error":"Import failed","category":"execution_failure",...}, no entries, no manifest; the advice ("check that the source tree is readable") does not explain the failure.
  • Control with malformed import { instead → exit 0, inventory_state: "partial", both files hashed, one parse_failures entry (Unexpected token (2:0)).

Root cause (verified in source, not just symptoms): import ""; parses fine, so extractImportDeclarations (src/domain/referenceSourceImportParsing.ts:129) emits a relationship with to: "" and resolution: "external" (resolveSpecifier("") → not relative, not absolute). In ReferenceSourceImportEntries.appendFile non-internal resolutions pass through untouched, and then sourceRelationshipSchema.to: z.string().min(1) (src/domain/referenceSourceGraph.ts:121) rejects the empty to — aborting the entire graph build instead of degrading to partial inventory like the malformed-declaration path does.

Fix shape: treat "" like the existing undefined case in appendRelationship (skip the edge) or emit it with parseState: "partial" and a placeholder to plus a limitation — either way the graph validates and the import degrades to partial like the Babel-failure path. Happy to open a PR with this if the readiness declaration route is confirmed.

<!-- gh-comment-id:6165408153 --> @nosmile99 commented on GitHub (Oct 14, 2026): Reproduced on current main (`322506d`) — the asymmetry holds exactly as described: **Repro** (via `scripts/rea.mjs import-reference-source <root> --format json`): - `main.mjs` containing `import "";` + unrelated `other.txt` → **exit 1**, `{"error":"Import failed","category":"execution_failure",...}`, no entries, no manifest; the advice ("check that the source tree is readable") does not explain the failure. - Control with malformed `import {` instead → **exit 0**, `inventory_state: "partial"`, both files hashed, one `parse_failures` entry (`Unexpected token (2:0)`). **Root cause** (verified in source, not just symptoms): `import "";` parses fine, so `extractImportDeclarations` (`src/domain/referenceSourceImportParsing.ts:129`) emits a relationship with `to: ""` and `resolution: "external"` (`resolveSpecifier("")` → not relative, not absolute). In `ReferenceSourceImportEntries.appendFile` non-internal resolutions pass through untouched, and then `sourceRelationshipSchema.to: z.string().min(1)` (`src/domain/referenceSourceGraph.ts:121`) rejects the empty `to` — aborting the entire graph build instead of degrading to `partial` inventory like the malformed-declaration path does. **Fix shape**: treat `""` like the existing `undefined` case in `appendRelationship` (skip the edge) or emit it with `parseState: "partial"` and a placeholder `to` plus a limitation — either way the graph validates and the import degrades to `partial` like the Babel-failure path. Happy to open a PR with this if the readiness declaration route is confirmed.
Author
Owner

@nosmile99 commented on GitHub (Oct 21, 2026):

Thanks for closing the loop on this — verified the fix PR resolves the empty-specifier case as described, and the inventory is retained as expected. Nice work, @morluto and @zpf2234.

<!-- gh-comment-id:6326591299 --> @nosmile99 commented on GitHub (Oct 21, 2026): Thanks for closing the loop on this — verified the fix PR resolves the empty-specifier case as described, and the inventory is retained as expected. Nice work, @morluto and @zpf2234.
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#1254
No description provided.