[GH-ISSUE #3401] [Bug] Unreadable reference source root loses its filesystem permission diagnostic #1265

Closed
opened 2026-10-16 21:03:32 +00:00 by bsakaguchi · 1 comment
Owner

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

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 an internal path-normalization validation error when the selected source directory is searchable but cannot be enumerated. It loses the original EACCES permission failure and selected directory path. The first command exits 1 with category: "execution_failure", and its primary diagnostic describes traversal/path normalization instead of the failed directory read.

The root failure was observed repeatedly. Restoring directory read permission makes the same import exit 0 and return a graph with a hashed main.txt entry. A separate unreadable nested-directory control retains its actual EACCES/opendir diagnostic and directory path in a partial graph while still hashing a readable sibling.

Reproduction

  1. Create a clean checkout, select the baseline and install the locked dependencies and compiled runtime.
git clone https://github.com/morluto/rea.git rea
cd rea
git checkout 8dde6370d0809ad04cda3342d4e6f290f48aa063
npm ci
npm run build:cached
  1. Run the following from the checkout root as a non-root macOS user on a filesystem that enforces directory permissions. The fixture contains one ordinary text file with the inline content below. Mode 0100 preserves directory search permission while removing enumeration permission. The script restores permissions before cleanup.
bash <<'SH'
work_dir="$(mktemp -d)"
trap 'chmod 700 "$work_dir"; rm -rf "$work_dir"' EXIT
printf 'root evidence\n' > "$work_dir/main.txt"
chmod 100 "$work_dir"
node scripts/rea.mjs import-reference-source "$work_dir" --json
printf 'exit=%s\n' "$?"
chmod 700 "$work_dir"
node scripts/rea.mjs import-reference-source "$work_dir" --json
printf 'exit=%s\n' "$?"
SH
  1. Compare the first failed import with the restored-permission import. The first returns the validation error below and exits 1; the second exits 0 and imports and hashes main.txt. The installation and build commands above are clean-checkout prerequisites; the reproduction used an already compiled checkout runtime.

Expected behavior

Report the directory-read permission failure, its selected path and directory-permission recovery guidance. Preserve the original I/O reason instead of replacing it with a graph-entry normalization error. The public category can remain execution_failure. After read permission is restored, import and hash the file normally.

docs/user-visible-errors.csv:41 assigns directory-permission guidance to reader I/O failures. src/application/ReferenceSourceImportTypes.ts:55 through src/application/ReferenceSourceImportTypes.ts:64 preserves the primary diagnostic. tests/boundary/cli/referenceSourceImportStatus.test.ts:155 through tests/boundary/cli/referenceSourceImportStatus.test.ts:190 asserts EACCES, the selected path and permission guidance for public permission errors.

Impact and scope

P2, supported. A supported failure path discards the actionable OS cause and directory identity and replaces them with an unrelated internal invariant. Readable inputs continue to work, and no source data is changed.

A workaround is available: restoring directory read permission was directly observed to make the same source import succeed. This requires authority to change that directory; otherwise, an owner must provide a readable copy or adjust its permissions.

REA version

6.4.0 (main at 8dde6370d0)

Runtime and platform

Node.js v26.10.0 on macOS 27.0.1, using the compiled checkout runtime. Architecture was not recorded for this reproduction.

Target and artifact context

A caller-selected local source directory containing one ordinary main.txt file with root evidence followed by a newline. The root has mode 0100 for the failure and 0700 for the recovery control. The separate nested-directory control has an unreadable child and a readable sibling.

Interface

CLI

Earliest failure stage

Artifact or evidence registration

Relevant output or evidence

The first import exits 1 and returns:

{"error":"Import failed","category":"execution_failure","message":"[\n  {\n    \"code\": \"custom\",\n    \"path\": [\n      \"entries\",\n      0,\n      \"path\"\n    ],\n    \"message\": \"Expected a normalized relative POSIX path without traversal\"\n  }\n] Reference source could not be indexed. Check that the source tree is readable, then try again."}

The output names neither EACCES nor the selected directory. The restored-permission control exits 0 and returns a graph with a hashed main.txt entry. The nested-directory control retains its EACCES/opendir diagnostic and directory path while hashing the readable sibling.

Typed outcome or recovery expected

Repeat the permission-controlled import. Its failure must retain the observed OS reason, selected directory identity and permission-specific recovery guidance, with no unrelated graph-path validation error. Restoring permissions must still import and hash the file. As the discriminating control, unreadable nested directories must continue to retain their own diagnostics and allow partial import of readable siblings. The public category can remain execution_failure.

Analysis or suspected cause

Source-derived reasoning: src/reference/ReferenceSourceReaderEntries.ts:128 through src/reference/ReferenceSourceReaderEntries.ts:137 records the failed root read as an entry. src/reference/ReferenceSourceReaderPaths.ts:5 maps that root to .. src/domain/referenceSourceGraph.ts:15 rejects that entry path, and src/application/ReferenceSourceImport.ts:284 through src/application/ReferenceSourceImport.ts:292 returns the graph-validation error instead of the original filesystem diagnostic. The public CLI output above agrees with this source path.

Additional context

  • #2551 concerns an overlong selected root escaping the typed reader result during admission; this failure occurs during directory enumeration and loses an already modeled permission error during graph validation.
  • #2652 concerns overlong child paths bypassing the import I/O result; this failure concerns the selected root's . entry and an EACCES diagnostic.
  • #787 concerns unavailable Windows no-follow support receiving permission advice; this failure is a real directory permission denial on macOS.
  • #2651 concerns graph validation rejecting a retained symlink to an excluded target; this reproduction contains no symlink or exclusion.
  • #1188 concerns the machine-readable category for an unreadable binary while retaining its EACCES text; this source import loses the filesystem reason and selected directory entirely.
  • #2545 concerns an unlistable child aborting a process filesystem snapshot; the reference-import nested-directory control here already preserves a partial graph and the original diagnostic.
  • #3387 concerns an empty module relationship target rejecting the inventory of a readable tree; this failure concerns a root directory entry rejecting the original permission diagnostic.
  • #3381 concerns file/index dependency lookup precedence in a successful import; this failure concerns root enumeration and diagnostic preservation.
  • #3383 concerns a package main declaration ignored during successful dependency lookup; this failure has only a text file and a denied directory read.
  • #3393 concerns false attribution of an overwritten resolver in a successful import; this failure concerns a filesystem error and graph-entry validation.
Originally created by @coygeek on GitHub (Oct 14, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3401 ### 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 an internal path-normalization validation error when the selected source directory is searchable but cannot be enumerated. It loses the original `EACCES` permission failure and selected directory path. The first command exits 1 with `category: "execution_failure"`, and its primary diagnostic describes traversal/path normalization instead of the failed directory read. The root failure was observed repeatedly. Restoring directory read permission makes the same import exit 0 and return a graph with a hashed `main.txt` entry. A separate unreadable nested-directory control retains its actual `EACCES`/`opendir` diagnostic and directory path in a partial graph while still hashing a readable sibling. ### Reproduction 1. Create a clean checkout, select the baseline and install the locked dependencies and compiled runtime. ```sh git clone https://github.com/morluto/rea.git rea cd rea git checkout 8dde6370d0809ad04cda3342d4e6f290f48aa063 npm ci npm run build:cached ``` 2. Run the following from the checkout root as a non-root macOS user on a filesystem that enforces directory permissions. The fixture contains one ordinary text file with the inline content below. Mode `0100` preserves directory search permission while removing enumeration permission. The script restores permissions before cleanup. ```sh bash <<'SH' work_dir="$(mktemp -d)" trap 'chmod 700 "$work_dir"; rm -rf "$work_dir"' EXIT printf 'root evidence\n' > "$work_dir/main.txt" chmod 100 "$work_dir" node scripts/rea.mjs import-reference-source "$work_dir" --json printf 'exit=%s\n' "$?" chmod 700 "$work_dir" node scripts/rea.mjs import-reference-source "$work_dir" --json printf 'exit=%s\n' "$?" SH ``` 3. Compare the first failed import with the restored-permission import. The first returns the validation error below and exits 1; the second exits 0 and imports and hashes `main.txt`. The installation and build commands above are clean-checkout prerequisites; the reproduction used an already compiled checkout runtime. ### Expected behavior Report the directory-read permission failure, its selected path and directory-permission recovery guidance. Preserve the original I/O reason instead of replacing it with a graph-entry normalization error. The public category can remain `execution_failure`. After read permission is restored, import and hash the file normally. `docs/user-visible-errors.csv:41` assigns directory-permission guidance to reader I/O failures. `src/application/ReferenceSourceImportTypes.ts:55` through `src/application/ReferenceSourceImportTypes.ts:64` preserves the primary diagnostic. `tests/boundary/cli/referenceSourceImportStatus.test.ts:155` through `tests/boundary/cli/referenceSourceImportStatus.test.ts:190` asserts `EACCES`, the selected path and permission guidance for public permission errors. ### Impact and scope P2, supported. A supported failure path discards the actionable OS cause and directory identity and replaces them with an unrelated internal invariant. Readable inputs continue to work, and no source data is changed. A workaround is available: restoring directory read permission was directly observed to make the same source import succeed. This requires authority to change that directory; otherwise, an owner must provide a readable copy or adjust its permissions. ### REA version 6.4.0 (main at 8dde6370d0809ad04cda3342d4e6f290f48aa063) ### Runtime and platform Node.js `v26.10.0` on macOS `27.0.1`, using the compiled checkout runtime. Architecture was not recorded for this reproduction. ### Target and artifact context A caller-selected local source directory containing one ordinary `main.txt` file with `root evidence` followed by a newline. The root has mode `0100` for the failure and `0700` for the recovery control. The separate nested-directory control has an unreadable child and a readable sibling. ### Interface CLI ### Earliest failure stage Artifact or evidence registration ### Relevant output or evidence The first import exits 1 and returns: ```text {"error":"Import failed","category":"execution_failure","message":"[\n {\n \"code\": \"custom\",\n \"path\": [\n \"entries\",\n 0,\n \"path\"\n ],\n \"message\": \"Expected a normalized relative POSIX path without traversal\"\n }\n] Reference source could not be indexed. Check that the source tree is readable, then try again."} ``` The output names neither `EACCES` nor the selected directory. The restored-permission control exits 0 and returns a graph with a hashed `main.txt` entry. The nested-directory control retains its `EACCES`/`opendir` diagnostic and directory path while hashing the readable sibling. ### Typed outcome or recovery expected Repeat the permission-controlled import. Its failure must retain the observed OS reason, selected directory identity and permission-specific recovery guidance, with no unrelated graph-path validation error. Restoring permissions must still import and hash the file. As the discriminating control, unreadable nested directories must continue to retain their own diagnostics and allow partial import of readable siblings. The public category can remain `execution_failure`. ### Analysis or suspected cause Source-derived reasoning: `src/reference/ReferenceSourceReaderEntries.ts:128` through `src/reference/ReferenceSourceReaderEntries.ts:137` records the failed root read as an entry. `src/reference/ReferenceSourceReaderPaths.ts:5` maps that root to `.`. `src/domain/referenceSourceGraph.ts:15` rejects that entry path, and `src/application/ReferenceSourceImport.ts:284` through `src/application/ReferenceSourceImport.ts:292` returns the graph-validation error instead of the original filesystem diagnostic. The public CLI output above agrees with this source path. ### Additional context - [#2551](https://github.com/morluto/rea/issues/2551) concerns an overlong selected root escaping the typed reader result during admission; this failure occurs during directory enumeration and loses an already modeled permission error during graph validation. - [#2652](https://github.com/morluto/rea/issues/2652) concerns overlong child paths bypassing the import I/O result; this failure concerns the selected root's `.` entry and an `EACCES` diagnostic. - [#787](https://github.com/morluto/rea/issues/787) concerns unavailable Windows no-follow support receiving permission advice; this failure is a real directory permission denial on macOS. - [#2651](https://github.com/morluto/rea/issues/2651) concerns graph validation rejecting a retained symlink to an excluded target; this reproduction contains no symlink or exclusion. - [#1188](https://github.com/morluto/rea/issues/1188) concerns the machine-readable category for an unreadable binary while retaining its `EACCES` text; this source import loses the filesystem reason and selected directory entirely. - [#2545](https://github.com/morluto/rea/issues/2545) concerns an unlistable child aborting a process filesystem snapshot; the reference-import nested-directory control here already preserves a partial graph and the original diagnostic. - [#3387](https://github.com/morluto/rea/issues/3387) concerns an empty module relationship target rejecting the inventory of a readable tree; this failure concerns a root directory entry rejecting the original permission diagnostic. - [#3381](https://github.com/morluto/rea/issues/3381) concerns file/index dependency lookup precedence in a successful import; this failure concerns root enumeration and diagnostic preservation. - [#3383](https://github.com/morluto/rea/issues/3383) concerns a package main declaration ignored during successful dependency lookup; this failure has only a text file and a denied directory read. - [#3393](https://github.com/morluto/rea/issues/3393) concerns false attribution of an overwritten resolver in a successful import; this failure concerns a filesystem error and graph-entry validation.
Author
Owner

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

Checked the reported fix on main at d87e73f2db.

Root enumeration failure is now stored separately as rootFailure instead of a directory entry at forbidden path dot. The reader returns an IO failure with that original diagnostic before graph construction while retaining partial facts; nested directories still use ordinary partial entries. The real-permission regression makes the root searchable/unreadable, asserts EACCES and selected root, restores permission and checks hashed main.txt.

Implementation: ReferenceSourceReaderEntries.ts, ReferenceSourceReader.ts.

The relevant regression checks in referenceSourceUnreadableDirectories.test.ts passed in the focused test run on this revision. This closure is based on the current source and those checks; provider fixtures do not establish a real-engine run. Closing as completed on main.

<!-- gh-comment-id:6380736885 --> @coygeek commented on GitHub (Oct 24, 2026): Checked the reported fix on `main` at d87e73f2db50be72d33a4b3c7199af35518b441c. Root enumeration failure is now stored separately as rootFailure instead of a directory entry at forbidden path dot. The reader returns an IO failure with that original diagnostic before graph construction while retaining partial facts; nested directories still use ordinary partial entries. The real-permission regression makes the root searchable/unreadable, asserts EACCES and selected root, restores permission and checks hashed main.txt. Implementation: [ReferenceSourceReaderEntries.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/reference/ReferenceSourceReaderEntries.ts#L43), [ReferenceSourceReader.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/reference/ReferenceSourceReader.ts#L94). The relevant regression checks in [referenceSourceUnreadableDirectories.test.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/tests/boundary/filesystem/referenceSourceUnreadableDirectories.test.ts) passed in the focused test run on this revision. This closure is based on the current source and those checks; provider fixtures do not establish a real-engine run. 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#1265
No description provided.