[GH-ISSUE #2551] fix(reference): overlong source roots reject instead of returning a typed failure #870

Closed
opened 2026-10-15 18:29:09 +00:00 by bsakaguchi · 1 comment
Owner

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

Summary

Reading a local reference-source tree with a root path consisting of a slash and 5000 ASCII characters rejects with the operating system's ENAMETOOLONG error. The source reader promises to model expected filesystem failures as a tagged Result, so callers lose the structured invalid-root or I/O outcome for this input. A valid short directory and a nonexistent short root still produce their expected Results.

Runtime or environment

Failure verified at a2d8b94dd5 and still present at 8b181716ac with Node v26.10.0, npm 11.19.1, Vitest 5.0.3 and lockfile-installed dependencies on macOS 27.0.1 arm64. The reproduction uses native filesystem operations with no cancellation or exclusion callback.

Steps to reproduce

  1. Use the source revision and installed dependencies stated above. Add a focused test as src/reference/referenceRootErrno.test.ts with the following body. The root is a generated string; no directory with that name needs to exist.
import { mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { expect, it } from "vitest";
import { ArtifactResourceScope } from "../artifacts/ArtifactResourceScope.js";
import { readReferenceSource } from "./ReferenceSourceReader.js";

it("returns a tagged failure for an overlong root", async () => {
  const scope = new ArtifactResourceScope();
  try {
    const result = await readReferenceSource(`/${"a".repeat(5000)}`, scope);
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error.tag).toBe("reference-source-reader");
      expect(["invalid-root", "io"]).toContain(result.error.code);
    }
  } finally {
    await scope.close();
  }
});

it("reads a short root and models a missing short root", async () => {
  const root = await mkdtemp(join(tmpdir(), "rea-root-control-"));
  const scope = new ArtifactResourceScope();
  try {
    await writeFile(join(root, "source.txt"), "readable");
    const read = await readReferenceSource(root, scope);
    expect(read.ok).toBe(true);
    if (read.ok) {
      expect(read.value.entries).toContainEqual(
        expect.objectContaining({ status: "read", kind: "file", path: "source.txt" }),
      );
    }
    const missing = await readReferenceSource(join(root, "absent"), scope);
    expect(missing).toMatchObject({
      ok: false,
      error: { tag: "reference-source-reader", code: "invalid-root" },
    });
  } finally {
    await scope.close();
    await rm(root, { recursive: true });
  }
});
  1. Run the exact focused test:
npm run test:focused -- src/reference/referenceRootErrno.test.ts
  1. Observe that the overlong-root test fails before it receives a Result. The equivalent verifier run used separate valid-directory and missing-root controls; both passed.

Expected behavior

When the operating system refuses the selected root because its name is too long, the reader should resolve to a failure Result containing the reference-source-reader tag, an invalid-root or io code, and the filesystem reason. This is an expected input or I/O failure at root admission. A valid short directory should still resolve to a successful inventory, and a nonexistent short root should still resolve to invalid-root.

Actual behavior

The focused verifier run exited with code 1. The overlong-root case rejected with ENAMETOOLONG: name too long, lstat, originating during root admission, instead of returning the tagged failure. The two control cases passed. No source entries had been collected before this root-admission failure.

Evidence

  • Expected source: AGENTS.md:29 requires tagged Results for expected boundary failures; src/reference/ReferenceSourceReader.ts:22-39 declares the reader Result boundary, and src/reference/ReferenceSourceReaderTypes.ts:66-77 defines its failure outcomes.
  • Failure source: The native focused reproduction rejected at src/reference/ReferenceSourceReaderValidate.ts:103. src/reference/ReferenceSourceReaderErrors.ts:24-38 omits ENAMETOOLONG; its classifier returns undefined at lines 64-65, the root catch rethrows at src/reference/ReferenceSourceReaderValidate.ts:128, and the reader catch rethrows non-ArtifactReaderFailure errors at src/reference/ReferenceSourceReader.ts:30.
  • Evidence provenance: observed
  • Local verification: reproduced
  • Reproduction completeness: complete

The source-import policy uses the same classifier at src/application/ReferenceSourceImportPolicy.ts:49-79, before invoking the reader. That corresponding import propagation is established from source; an import or CLI runtime reproduction was not run. This report makes no claim about lost mid-traversal entries or an unhandled process crash.

Restoration check

Repeat the overlong-root case and confirm that it resolves to the tagged failure Result with the native reason instead of rejecting. Repeat the short-root controls and confirm that the readable file remains in the successful inventory and that the nonexistent root remains invalid-root. Keep unexpected programmer invariant errors observable; restoration does not require converting arbitrary exceptions into filesystem outcomes.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: A bounded local root-selection error escapes the reader's structured failure contract. The probe establishes incorrect error reporting for this input, with no demonstrated corruption, loss of collected source inventory, or outage for valid roots.
  • Workaround status: available
  • Workaround basis: Selecting an existing short root avoids the demonstrated failure and returns its file inventory, as directly observed in the control. This permits a corrected root selection but does not restore structured reporting for the overlong input.
Originally created by @coygeek on GitHub (Oct 13, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/2551 ## Summary Reading a local reference-source tree with a root path consisting of a slash and 5000 ASCII characters rejects with the operating system's ENAMETOOLONG error. The source reader promises to model expected filesystem failures as a tagged Result, so callers lose the structured invalid-root or I/O outcome for this input. A valid short directory and a nonexistent short root still produce their expected Results. ## Runtime or environment Failure verified at a2d8b94dd50dab8d966239dd63ab84718aa8b4b4 and still present at 8b181716aca2cb467be499cba226abfb5ae19141 with Node v26.10.0, npm 11.19.1, Vitest 5.0.3 and lockfile-installed dependencies on macOS 27.0.1 arm64. The reproduction uses native filesystem operations with no cancellation or exclusion callback. ## Steps to reproduce 1. Use the source revision and installed dependencies stated above. Add a focused test as `src/reference/referenceRootErrno.test.ts` with the following body. The root is a generated string; no directory with that name needs to exist. ```ts import { mkdtemp, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { expect, it } from "vitest"; import { ArtifactResourceScope } from "../artifacts/ArtifactResourceScope.js"; import { readReferenceSource } from "./ReferenceSourceReader.js"; it("returns a tagged failure for an overlong root", async () => { const scope = new ArtifactResourceScope(); try { const result = await readReferenceSource(`/${"a".repeat(5000)}`, scope); expect(result.ok).toBe(false); if (!result.ok) { expect(result.error.tag).toBe("reference-source-reader"); expect(["invalid-root", "io"]).toContain(result.error.code); } } finally { await scope.close(); } }); it("reads a short root and models a missing short root", async () => { const root = await mkdtemp(join(tmpdir(), "rea-root-control-")); const scope = new ArtifactResourceScope(); try { await writeFile(join(root, "source.txt"), "readable"); const read = await readReferenceSource(root, scope); expect(read.ok).toBe(true); if (read.ok) { expect(read.value.entries).toContainEqual( expect.objectContaining({ status: "read", kind: "file", path: "source.txt" }), ); } const missing = await readReferenceSource(join(root, "absent"), scope); expect(missing).toMatchObject({ ok: false, error: { tag: "reference-source-reader", code: "invalid-root" }, }); } finally { await scope.close(); await rm(root, { recursive: true }); } }); ``` 2. Run the exact focused test: ```sh npm run test:focused -- src/reference/referenceRootErrno.test.ts ``` 3. Observe that the overlong-root test fails before it receives a Result. The equivalent verifier run used separate valid-directory and missing-root controls; both passed. ## Expected behavior When the operating system refuses the selected root because its name is too long, the reader should resolve to a failure Result containing the reference-source-reader tag, an invalid-root or io code, and the filesystem reason. This is an expected input or I/O failure at root admission. A valid short directory should still resolve to a successful inventory, and a nonexistent short root should still resolve to invalid-root. ## Actual behavior The focused verifier run exited with code 1. The overlong-root case rejected with `ENAMETOOLONG: name too long, lstat`, originating during root admission, instead of returning the tagged failure. The two control cases passed. No source entries had been collected before this root-admission failure. ## Evidence - Expected source: `AGENTS.md:29` requires tagged Results for expected boundary failures; `src/reference/ReferenceSourceReader.ts:22-39` declares the reader Result boundary, and `src/reference/ReferenceSourceReaderTypes.ts:66-77` defines its failure outcomes. - Failure source: The native focused reproduction rejected at `src/reference/ReferenceSourceReaderValidate.ts:103`. `src/reference/ReferenceSourceReaderErrors.ts:24-38` omits ENAMETOOLONG; its classifier returns undefined at lines 64-65, the root catch rethrows at `src/reference/ReferenceSourceReaderValidate.ts:128`, and the reader catch rethrows non-ArtifactReaderFailure errors at `src/reference/ReferenceSourceReader.ts:30`. - Evidence provenance: observed - Local verification: reproduced - Reproduction completeness: complete The source-import policy uses the same classifier at `src/application/ReferenceSourceImportPolicy.ts:49-79`, before invoking the reader. That corresponding import propagation is established from source; an import or CLI runtime reproduction was not run. This report makes no claim about lost mid-traversal entries or an unhandled process crash. ## Restoration check Repeat the overlong-root case and confirm that it resolves to the tagged failure Result with the native reason instead of rejecting. Repeat the short-root controls and confirm that the readable file remains in the successful inventory and that the nonexistent root remains invalid-root. Keep unexpected programmer invariant errors observable; restoration does not require converting arbitrary exceptions into filesystem outcomes. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: A bounded local root-selection error escapes the reader's structured failure contract. The probe establishes incorrect error reporting for this input, with no demonstrated corruption, loss of collected source inventory, or outage for valid roots. - Workaround status: available - Workaround basis: Selecting an existing short root avoids the demonstrated failure and returns its file inventory, as directly observed in the control. This permits a corrected root selection but does not restore structured reporting for the overlong input.
bsakaguchi 2026-10-15 18:29:09 +00:00
Author
Owner

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

Addressed on main in ec271121e.

Overlong selected reference roots return the tagged reader failure with the native reason.

Validation: Six reference-source failure-detail cases passed, including the overlong-root control.

Verification scope: No change to host path-length limits.

<!-- gh-comment-id:6158656171 --> @morluto commented on GitHub (Oct 14, 2026): Addressed on main in [ec271121e](https://github.com/morluto/rea/commit/ec271121e). Overlong selected reference roots return the tagged reader failure with the native reason. Validation: Six reference-source failure-detail cases passed, including the overlong-root control. Verification scope: No change to host path-length limits.
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#870
No description provided.