[GH-ISSUE #3381] [Bug] Directory index overrides an existing CommonJS file during reference-source import #1248

Closed
opened 2026-10-16 21:03:19 +00:00 by bsakaguchi · 0 comments
Owner

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

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 reports that require("./dep") in a CommonJS file depends on the extensionless file dep/index when both that file and dep.js are present. Node resolves the same call to dep.js. The historical graph therefore records a parsed internal dependency on the wrong file, which can mislead analysis using the saved graph.

The CLI exits successfully. The same mismatch was observed on the initial run and an independent rerun. As a control, importing the same main.cjs and dep.js without dep/index returns the correct internal dependency on dep.js. The erroneous target is therefore caused by the additional index candidate, rather than an inability to parse the CommonJS call.

Reproduction

  1. Start from a clean source checkout and install the locked dependencies, then build the runtime:
git clone https://github.com/morluto/rea.git rea-repro
cd rea-repro
git checkout --detach 8dde6370d0809ad04cda3342d4e6f290f48aa063
npm ci
npm run build:cached
  1. From the repository root, run this isolated reproduction. It creates every input file and an isolated configuration home. The resolver control only looks up a filename; it does not execute either dependency.
rea_cli="$PWD/scripts/rea.mjs"
work="$(mktemp -d "${TMPDIR:-.}/rea-import.XXXXXX")"
mkdir -p "$work/source/dep" "$work/home"
printf '%s\n' 'require("./dep");' > "$work/source/main.cjs"
printf '%s\n' 'module.exports = "real dependency";' > "$work/source/dep.js"
printf '%s\n' 'module.exports = "wrong dependency";' > "$work/source/dep/index"
HOME="$work/home" XDG_CONFIG_HOME="$work/home" node "$rea_cli" import-reference-source "$work/source" --json > "$work/graph.json"
node - "$work/source" "$work/graph.json" <<'NODE'
const fs = require("node:fs");
const path = require("node:path");
const { createRequire } = require("node:module");
const root = fs.realpathSync(process.argv[2]);
const graph = JSON.parse(fs.readFileSync(process.argv[3], "utf8"));
console.log("Node target:", path.relative(root, createRequire(path.join(root, "main.cjs")).resolve("./dep")));
console.log("REA relationships:", JSON.stringify(graph.relationships));
console.log("Parse failures:", JSON.stringify(graph.parse_failures));
NODE
rm -rf "$work"
  1. Compare the Node target with the REA relationship target in the output below. For the file-presence control, repeat step 2 while omitting the command that creates dep/index; both targets should then be dep.js, as observed in the control run.

Expected behavior

The CommonJS dependency should point to dep.js, matching Node's resolution of the supplied source. An unrelated file under dep/ must not replace that dependency with a different resolved target. The importer already supports CommonJS file and directory imports and checks them against Node's loader in its public CLI regression coverage.

docs/cli.md:223 documents the complete historical-source graph. tests/boundary/cli/referenceSourceDirectoryImports.test.ts:67 establishes CommonJS import meaning with createRequire().resolve(), and the assertions at tests/boundary/cli/referenceSourceDirectoryImports.test.ts:96 require the public CLI to return those internal dependency targets.

Impact and scope

Impact level: P2. Assessment status: supported. A supported import command produces a wrong dependency in the committed historical graph for a bounded source layout. The source bytes and inventory remain available, and no file damage was observed.

Workaround status: available. The executed Node resolve-only control identifies the correct target without running the source or changing its bytes. A caller can use that result when interpreting the affected relationship, although the saved REA graph remains incorrect.

REA version

6.4.0 (main at 8dde6370d0)

Runtime and platform

Node.js v26.10.0; macOS 27.0.1 arm64. Dependencies and the compiled CLI runtime were already present during the observed reproduction. No native analysis provider is required.

Target and artifact context

A local historical-source tree containing main.cjs, dep.js, and the extensionless file dep/index, with different contents in the two dependency candidates. The result is the historical-source graph returned by import-reference-source --json.

Interface

CLI

Earliest failure stage

Artifact or evidence registration

Relevant output or evidence

The successful CLI run and independent Node resolver control produced:

Node target: dep.js
REA relationships: [{"from_path":"main.cjs","to":"dep/index","kind":"requires","resolution":"internal","parse_state":"parsed"}]
Parse failures: []

The evidence was observed locally, the failure was reproduced, and the reproduction is complete.

Typed outcome or recovery expected

With all three fixture files present and unchanged, the command must return the dependency from main.cjs to dep.js, with the existing successful parse preserved. Retain a control where main.cjs requires a directory containing only dep/index.js; that ordinary directory fallback must still resolve to dep/index.js. The file-presence control described above must continue to resolve to dep.js.

Analysis or suspected cause

Source-derived reasoning: src/application/ReferenceSourceImportEntries.ts:112 defines the suffix candidates, and the following candidate construction interleaves filename and index candidates, so dep/index is considered before dep.js. The selected match becomes the graph target at src/application/ReferenceSourceImportEntries.ts:164. This reasoning is consistent with the executed output and the file-presence control.

Additional context

Related completed report #2022 concerns unsupported .cjs suffix lookup during JavaScript application analysis; this report concerns a directory index taking precedence over an existing .js file during historical-source import.

Related completed report #2020 concerns an empty package main suppressing directory fallback; this fixture has no package metadata and must prefer an existing file over directory lookup.

Related completed report #1811 concerns missing TypeScript emitted-source substitutions in application analysis; this fixture contains present JavaScript files and no TypeScript substitution.

Related completed reports #2021 and #1039 concern inappropriate ESM and HTML extension/directory fallbacks; this report concerns valid CommonJS file lookup and its precedence in a different importer.

Related completed report #1174 concerns inappropriate fallback for selected package exports targets; this fixture has no exports metadata and requires ordinary CommonJS file-before-directory precedence.

Related completed report #1761 concerns a sibling node_modules file overriding package exports; this fixture is a relative require with no package exports, and the wrong target is a directory index.

Related open report #3307 concerns ESM and HTML URL-segment normalization in application analysis; this fixture has no URL-segment ambiguity and fails file/index ordering in historical-source import.

Originally created by @coygeek on GitHub (Oct 14, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3381 ### 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` reports that `require("./dep")` in a CommonJS file depends on the extensionless file `dep/index` when both that file and `dep.js` are present. Node resolves the same call to `dep.js`. The historical graph therefore records a parsed internal dependency on the wrong file, which can mislead analysis using the saved graph. The CLI exits successfully. The same mismatch was observed on the initial run and an independent rerun. As a control, importing the same `main.cjs` and `dep.js` without `dep/index` returns the correct internal dependency on `dep.js`. The erroneous target is therefore caused by the additional index candidate, rather than an inability to parse the CommonJS call. ### Reproduction 1. Start from a clean source checkout and install the locked dependencies, then build the runtime: ```sh git clone https://github.com/morluto/rea.git rea-repro cd rea-repro git checkout --detach 8dde6370d0809ad04cda3342d4e6f290f48aa063 npm ci npm run build:cached ``` 2. From the repository root, run this isolated reproduction. It creates every input file and an isolated configuration home. The resolver control only looks up a filename; it does not execute either dependency. ```sh rea_cli="$PWD/scripts/rea.mjs" work="$(mktemp -d "${TMPDIR:-.}/rea-import.XXXXXX")" mkdir -p "$work/source/dep" "$work/home" printf '%s\n' 'require("./dep");' > "$work/source/main.cjs" printf '%s\n' 'module.exports = "real dependency";' > "$work/source/dep.js" printf '%s\n' 'module.exports = "wrong dependency";' > "$work/source/dep/index" HOME="$work/home" XDG_CONFIG_HOME="$work/home" node "$rea_cli" import-reference-source "$work/source" --json > "$work/graph.json" node - "$work/source" "$work/graph.json" <<'NODE' const fs = require("node:fs"); const path = require("node:path"); const { createRequire } = require("node:module"); const root = fs.realpathSync(process.argv[2]); const graph = JSON.parse(fs.readFileSync(process.argv[3], "utf8")); console.log("Node target:", path.relative(root, createRequire(path.join(root, "main.cjs")).resolve("./dep"))); console.log("REA relationships:", JSON.stringify(graph.relationships)); console.log("Parse failures:", JSON.stringify(graph.parse_failures)); NODE rm -rf "$work" ``` 3. Compare the Node target with the REA relationship target in the output below. For the file-presence control, repeat step 2 while omitting the command that creates `dep/index`; both targets should then be `dep.js`, as observed in the control run. ### Expected behavior The CommonJS dependency should point to `dep.js`, matching Node's resolution of the supplied source. An unrelated file under `dep/` must not replace that dependency with a different resolved target. The importer already supports CommonJS file and directory imports and checks them against Node's loader in its public CLI regression coverage. `docs/cli.md:223` documents the complete historical-source graph. `tests/boundary/cli/referenceSourceDirectoryImports.test.ts:67` establishes CommonJS import meaning with `createRequire().resolve()`, and the assertions at `tests/boundary/cli/referenceSourceDirectoryImports.test.ts:96` require the public CLI to return those internal dependency targets. ### Impact and scope Impact level: P2. Assessment status: supported. A supported import command produces a wrong dependency in the committed historical graph for a bounded source layout. The source bytes and inventory remain available, and no file damage was observed. Workaround status: available. The executed Node resolve-only control identifies the correct target without running the source or changing its bytes. A caller can use that result when interpreting the affected relationship, although the saved REA graph remains incorrect. ### REA version 6.4.0 (main at 8dde6370d0809ad04cda3342d4e6f290f48aa063) ### Runtime and platform Node.js v26.10.0; macOS 27.0.1 arm64. Dependencies and the compiled CLI runtime were already present during the observed reproduction. No native analysis provider is required. ### Target and artifact context A local historical-source tree containing `main.cjs`, `dep.js`, and the extensionless file `dep/index`, with different contents in the two dependency candidates. The result is the historical-source graph returned by `import-reference-source --json`. ### Interface CLI ### Earliest failure stage Artifact or evidence registration ### Relevant output or evidence The successful CLI run and independent Node resolver control produced: ```text Node target: dep.js REA relationships: [{"from_path":"main.cjs","to":"dep/index","kind":"requires","resolution":"internal","parse_state":"parsed"}] Parse failures: [] ``` The evidence was observed locally, the failure was reproduced, and the reproduction is complete. ### Typed outcome or recovery expected With all three fixture files present and unchanged, the command must return the dependency from `main.cjs` to `dep.js`, with the existing successful parse preserved. Retain a control where `main.cjs` requires a directory containing only `dep/index.js`; that ordinary directory fallback must still resolve to `dep/index.js`. The file-presence control described above must continue to resolve to `dep.js`. ### Analysis or suspected cause Source-derived reasoning: `src/application/ReferenceSourceImportEntries.ts:112` defines the suffix candidates, and the following candidate construction interleaves filename and index candidates, so `dep/index` is considered before `dep.js`. The selected match becomes the graph target at `src/application/ReferenceSourceImportEntries.ts:164`. This reasoning is consistent with the executed output and the file-presence control. ### Additional context Related completed report [#2022](https://github.com/morluto/rea/issues/2022) concerns unsupported `.cjs` suffix lookup during JavaScript application analysis; this report concerns a directory index taking precedence over an existing `.js` file during historical-source import. Related completed report [#2020](https://github.com/morluto/rea/issues/2020) concerns an empty package main suppressing directory fallback; this fixture has no package metadata and must prefer an existing file over directory lookup. Related completed report [#1811](https://github.com/morluto/rea/issues/1811) concerns missing TypeScript emitted-source substitutions in application analysis; this fixture contains present JavaScript files and no TypeScript substitution. Related completed reports [#2021](https://github.com/morluto/rea/issues/2021) and [#1039](https://github.com/morluto/rea/issues/1039) concern inappropriate ESM and HTML extension/directory fallbacks; this report concerns valid CommonJS file lookup and its precedence in a different importer. Related completed report [#1174](https://github.com/morluto/rea/issues/1174) concerns inappropriate fallback for selected package exports targets; this fixture has no exports metadata and requires ordinary CommonJS file-before-directory precedence. Related completed report [#1761](https://github.com/morluto/rea/issues/1761) concerns a sibling node_modules file overriding package exports; this fixture is a relative require with no package exports, and the wrong target is a directory index. Related open report [#3307](https://github.com/morluto/rea/issues/3307) concerns ESM and HTML URL-segment normalization in application analysis; this fixture has no URL-segment ambiguity and fails file/index ordering in historical-source import.
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#1248
No description provided.