[GH-ISSUE #3332] [Bug] Named-pipe JavaScript recovery input blocks cancellation and the deadline #1201

Closed
opened 2026-10-15 18:31:58 +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/3332

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

Selecting a named pipe with no writer can leave JavaScript recovery waiting indefinitely before it rejects the nonregular input. The production reader probe remained unsettled after cancellation and required termination. The regular-file control completed, and repeated runs produced the same result.

The public recover-javascript-sources command on supported Linux x64 reaches this input read before invoking Wakaru according to source inspection. No public Linux runtime reproduction or elapsed 120-second deadline observation was executed. Public-path deadline and cancellation failure are established by static proof; the local executed observation covers the compiled production reader.

Reproduction

  1. Start from a clean checkout at the baseline and install the locked dependencies and build the runtime. These are clean-checkout prerequisites; the reader observation used the built runtime.
git clone https://github.com/morluto/rea.git
cd rea
git checkout 8dde6370d0809ad04cda3342d4e6f290f48aa063
npm ci
npm run build:cached
  1. From the repository root, run the bounded production-reader probe below. It creates every local input inline, aborts the read after 100 milliseconds, checks settlement after 750 milliseconds, and terminates only its own child if it remains blocked after 1.5 seconds. The regular-file control uses the same reader and cancellation signal. Leave the FIFO without a writer.
work="$(mktemp -d)"
mkdir "$work/home"
cat > "$work/reader.mjs" <<'JS'
import { pathToFileURL } from "node:url";
import { join } from "node:path";
const { readRecoveryFile } = await import(pathToFileURL(
  join(process.argv[2], "dist/javascript/recovery/RecoveryFiles.js")));
const controller = new AbortController();
let settled = false;
const reading = readRecoveryFile(process.argv[3], 1024, controller.signal).then(
  bytes => { settled = true; console.log(JSON.stringify({ status: "resolved", bytes: bytes.length })); },
  error => { settled = true; console.log(JSON.stringify({ status: "rejected", tag: error._tag })); });
setTimeout(() => { controller.abort(); console.log("abort_requested"); }, 100);
setTimeout(() => console.log(JSON.stringify({ after_ms: 750, settled })), 750);
await reading;
JS
python3 - "$PWD" "$work" <<'PY'
import os
from pathlib import Path
import subprocess
import sys
checkout, work = sys.argv[1], Path(sys.argv[2])
(work / 'regular.js').write_text('module.exports = 1;\n')
fifo = work / 'sample.js'
os.mkfifo(fifo)
try:
    for name in ['regular.js', 'sample.js']:
        child = subprocess.Popen(['node', str(work / 'reader.mjs'), checkout, str(work / name)], env={**os.environ, 'HOME': str(work / 'home')}, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
        timed_out = False
        try:
            output, error = child.communicate(timeout=1.5)
        except subprocess.TimeoutExpired:
            timed_out = True
            child.terminate()
            try:
                output, error = child.communicate(timeout=1)
            except subprocess.TimeoutExpired:
                child.kill()
                output, error = child.communicate()
        print(name, 'bounded_timeout=' + str(timed_out), 'returncode=' + str(child.returncode))
        print(output, end='')
        print(error, end='')
finally:
    fifo.unlink()
PY
  1. Observe that regular.js resolves with 20 bytes and settled: true, while sample.js remains at settled: false after abortion and requires bounded termination. The probe was executed locally on macOS and produced the output in Relevant output or evidence.

  2. The public trigger below was not executed locally. On supported Linux x64, select an admitted caller-supplied Wakaru executable through REA_WAKARU_COMMAND and configure util-linux prlimit. Both the engine and limiter must be regular readable/executable files. From the repository root, create the selected FIFO and isolated HOME, then run the public command. There must be no writer opening sample.js; the output directory must be absent with an existing parent, and no additional input is selected.

work="$(mktemp -d)"
mkdir "$work/home"
mkfifo "$work/sample.js"
HOME="$work/home" node scripts/rea.mjs recover-javascript-sources "$work/sample.js" "$work/output" --json

Expected behavior

Recovery must reject a selected nonregular input with a typed error and settle its owned acquisition. Cancellation and the documented 120-second operation deadline must not remain pending behind a blocking file open. A regular source file must remain readable and recoverable.

docs/javascript-recovery.md:132 specifies a 120-second operation deadline and explicit failure at limits. src/javascript/recovery/RecoveryFiles.ts:31 requires a regular file before accepting its contents.

Impact and scope

P2, supported. A bounded invalid local input blocks recovery and prevents cooperative cancellation. The production reader failure was observed; public Linux reachability is proven statically, while its elapsed-time behavior was not separately executed.

A workaround is available. Supplying a regular UTF-8 JavaScript file avoids the blocking pipe open, and the executed regular-file reader control completed. This preserves the selected-file workflow without changing source or relaxing a boundary.

REA version

6.4.0 (main at 8dde6370d0)

Runtime and platform

Node.js v26.10.0; macOS 27.0.1 for the executed reader probe. The probe host architecture was not recorded. Public recovery is supported on Linux x64 with caller-supplied Wakaru and util-linux prlimit; that public runtime was not available locally.

Target and artifact context

A caller-selected local FIFO named sample.js, created with mkfifo and left without a writer, plus a regular-file control named regular.js containing module.exports = 1; and a newline. The bounded probe reads through dist/javascript/recovery/RecoveryFiles.js with a 1024-byte limit and an AbortController signal. The public command selects only the FIFO and an absent output directory with an existing parent.

Interface

CLI

Earliest failure stage

Cleanup, cancellation, or lifecycle

Relevant output or evidence

The compiled-reader probe produced the same result on repeated runs:

regular.js bounded_timeout=False returncode=0
{"status":"resolved","bytes":20}
abort_requested
{"after_ms":750,"settled":true}
sample.js bounded_timeout=True returncode=-15
abort_requested
{"after_ms":750,"settled":false}

Typed outcome or recovery expected

On supported Linux, select the FIFO through the public command and verify prompt typed rejection, no published output, and settled cleanup. Repeat acquisition cancellation and confirm the command settles without forced termination. Keep a regular UTF-8 JavaScript recovery control that succeeds with the configured engine. The bounded reader probe must also settle its FIFO case rather than remain pending after abortion.

Analysis or suspected cause

Source-derived reasoning: src/javascript/recovery/RecoveryFiles.ts:28 awaits a blocking read-only open before file-type and cancellation checks. The nonregular check at src/javascript/recovery/RecoveryFiles.ts:31 therefore lies after the pending open. The CLI's interrupt handlers abort the same signal, which does not interrupt the pending open.

Public reachability is established by src/cli/javascriptRecoveryCommands.ts:56, src/application/javascript/JavaScriptRecoveryService.ts:35, src/javascript/recovery/WakaruCommand.ts:65, and src/javascript/recovery/WakaruProvider.ts:107. The CLI accepts string input/output paths and forwards them to the shared service. The service checks schema, absolute paths, and distinct selected inputs without checking file type. On Linux x64, the stated executable configuration passes command admission.

The provider creates its deadline, prepares its private workspace, and awaits input snapshots. A single input uses the unchanged snapshot filename bundle.js; its absolute selected path reaches the production reader. Wakaru version checking and every later deadline check lie after snapshot acquisition. src/cli/commandCancellation.ts turns interrupts into signal abortion, which the blocked open does not consume. These premises do not depend on malformed engine output, a running application, or an external service.

Evidence provenance and local verification for the public-path claim are static proof, with complete reproduction instructions. The compiled-reader output is executed local evidence; the public Linux command and its elapsed-time behavior remain unexecuted.

Additional context

  • Closed completed #3218 concerns FIFO entry kinds and inflated historical-source import counts; that import rejects the nonregular input without opening it, while this report concerns blocked JavaScript recovery acquisition and cancellation.
  • Open #3197 concerns an interrupted binary-target admission that settles with UNKNOWN instead of a typed cancellation result; this report concerns a recovery read that never settles after cancellation.
  • Closed completed #3198 concerns composed native MCP operations swallowing an already typed provider cancellation; this report concerns a pending filesystem open before the recovery engine starts.
  • Closed completed #827 introduced JavaScript source recovery and its cancellation contract; it does not report the named-pipe acquisition defect.
  • Closed completed #1605 added recovery of related scripts together; this report selects one FIFO and requires its acquisition to reject or settle promptly.
Originally created by @coygeek on GitHub (Oct 14, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3332 ### 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 Selecting a named pipe with no writer can leave JavaScript recovery waiting indefinitely before it rejects the nonregular input. The production reader probe remained unsettled after cancellation and required termination. The regular-file control completed, and repeated runs produced the same result. The public `recover-javascript-sources` command on supported Linux x64 reaches this input read before invoking Wakaru according to source inspection. No public Linux runtime reproduction or elapsed 120-second deadline observation was executed. Public-path deadline and cancellation failure are established by static proof; the local executed observation covers the compiled production reader. ### Reproduction 1. Start from a clean checkout at the baseline and install the locked dependencies and build the runtime. These are clean-checkout prerequisites; the reader observation used the built runtime. ```sh git clone https://github.com/morluto/rea.git cd rea git checkout 8dde6370d0809ad04cda3342d4e6f290f48aa063 npm ci npm run build:cached ``` 2. From the repository root, run the bounded production-reader probe below. It creates every local input inline, aborts the read after 100 milliseconds, checks settlement after 750 milliseconds, and terminates only its own child if it remains blocked after 1.5 seconds. The regular-file control uses the same reader and cancellation signal. Leave the FIFO without a writer. ```sh work="$(mktemp -d)" mkdir "$work/home" cat > "$work/reader.mjs" <<'JS' import { pathToFileURL } from "node:url"; import { join } from "node:path"; const { readRecoveryFile } = await import(pathToFileURL( join(process.argv[2], "dist/javascript/recovery/RecoveryFiles.js"))); const controller = new AbortController(); let settled = false; const reading = readRecoveryFile(process.argv[3], 1024, controller.signal).then( bytes => { settled = true; console.log(JSON.stringify({ status: "resolved", bytes: bytes.length })); }, error => { settled = true; console.log(JSON.stringify({ status: "rejected", tag: error._tag })); }); setTimeout(() => { controller.abort(); console.log("abort_requested"); }, 100); setTimeout(() => console.log(JSON.stringify({ after_ms: 750, settled })), 750); await reading; JS python3 - "$PWD" "$work" <<'PY' import os from pathlib import Path import subprocess import sys checkout, work = sys.argv[1], Path(sys.argv[2]) (work / 'regular.js').write_text('module.exports = 1;\n') fifo = work / 'sample.js' os.mkfifo(fifo) try: for name in ['regular.js', 'sample.js']: child = subprocess.Popen(['node', str(work / 'reader.mjs'), checkout, str(work / name)], env={**os.environ, 'HOME': str(work / 'home')}, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) timed_out = False try: output, error = child.communicate(timeout=1.5) except subprocess.TimeoutExpired: timed_out = True child.terminate() try: output, error = child.communicate(timeout=1) except subprocess.TimeoutExpired: child.kill() output, error = child.communicate() print(name, 'bounded_timeout=' + str(timed_out), 'returncode=' + str(child.returncode)) print(output, end='') print(error, end='') finally: fifo.unlink() PY ``` 3. Observe that `regular.js` resolves with 20 bytes and `settled: true`, while `sample.js` remains at `settled: false` after abortion and requires bounded termination. The probe was executed locally on macOS and produced the output in Relevant output or evidence. 4. The public trigger below was not executed locally. On supported Linux x64, select an admitted caller-supplied Wakaru executable through `REA_WAKARU_COMMAND` and configure util-linux `prlimit`. Both the engine and limiter must be regular readable/executable files. From the repository root, create the selected FIFO and isolated HOME, then run the public command. There must be no writer opening `sample.js`; the output directory must be absent with an existing parent, and no additional input is selected. ```sh work="$(mktemp -d)" mkdir "$work/home" mkfifo "$work/sample.js" HOME="$work/home" node scripts/rea.mjs recover-javascript-sources "$work/sample.js" "$work/output" --json ``` ### Expected behavior Recovery must reject a selected nonregular input with a typed error and settle its owned acquisition. Cancellation and the documented 120-second operation deadline must not remain pending behind a blocking file open. A regular source file must remain readable and recoverable. `docs/javascript-recovery.md:132` specifies a 120-second operation deadline and explicit failure at limits. `src/javascript/recovery/RecoveryFiles.ts:31` requires a regular file before accepting its contents. ### Impact and scope P2, supported. A bounded invalid local input blocks recovery and prevents cooperative cancellation. The production reader failure was observed; public Linux reachability is proven statically, while its elapsed-time behavior was not separately executed. A workaround is available. Supplying a regular UTF-8 JavaScript file avoids the blocking pipe open, and the executed regular-file reader control completed. This preserves the selected-file workflow without changing source or relaxing a boundary. ### REA version 6.4.0 (main at 8dde6370d0809ad04cda3342d4e6f290f48aa063) ### Runtime and platform Node.js v26.10.0; macOS 27.0.1 for the executed reader probe. The probe host architecture was not recorded. Public recovery is supported on Linux x64 with caller-supplied Wakaru and util-linux `prlimit`; that public runtime was not available locally. ### Target and artifact context A caller-selected local FIFO named `sample.js`, created with `mkfifo` and left without a writer, plus a regular-file control named `regular.js` containing `module.exports = 1;` and a newline. The bounded probe reads through `dist/javascript/recovery/RecoveryFiles.js` with a 1024-byte limit and an AbortController signal. The public command selects only the FIFO and an absent output directory with an existing parent. ### Interface CLI ### Earliest failure stage Cleanup, cancellation, or lifecycle ### Relevant output or evidence The compiled-reader probe produced the same result on repeated runs: ```text regular.js bounded_timeout=False returncode=0 {"status":"resolved","bytes":20} abort_requested {"after_ms":750,"settled":true} sample.js bounded_timeout=True returncode=-15 abort_requested {"after_ms":750,"settled":false} ``` ### Typed outcome or recovery expected On supported Linux, select the FIFO through the public command and verify prompt typed rejection, no published output, and settled cleanup. Repeat acquisition cancellation and confirm the command settles without forced termination. Keep a regular UTF-8 JavaScript recovery control that succeeds with the configured engine. The bounded reader probe must also settle its FIFO case rather than remain pending after abortion. ### Analysis or suspected cause Source-derived reasoning: `src/javascript/recovery/RecoveryFiles.ts:28` awaits a blocking read-only open before file-type and cancellation checks. The nonregular check at `src/javascript/recovery/RecoveryFiles.ts:31` therefore lies after the pending open. The CLI's interrupt handlers abort the same signal, which does not interrupt the pending open. Public reachability is established by `src/cli/javascriptRecoveryCommands.ts:56`, `src/application/javascript/JavaScriptRecoveryService.ts:35`, `src/javascript/recovery/WakaruCommand.ts:65`, and `src/javascript/recovery/WakaruProvider.ts:107`. The CLI accepts string input/output paths and forwards them to the shared service. The service checks schema, absolute paths, and distinct selected inputs without checking file type. On Linux x64, the stated executable configuration passes command admission. The provider creates its deadline, prepares its private workspace, and awaits input snapshots. A single input uses the unchanged snapshot filename `bundle.js`; its absolute selected path reaches the production reader. Wakaru version checking and every later deadline check lie after snapshot acquisition. `src/cli/commandCancellation.ts` turns interrupts into signal abortion, which the blocked open does not consume. These premises do not depend on malformed engine output, a running application, or an external service. Evidence provenance and local verification for the public-path claim are static proof, with complete reproduction instructions. The compiled-reader output is executed local evidence; the public Linux command and its elapsed-time behavior remain unexecuted. ### Additional context - Closed completed [#3218](https://github.com/morluto/rea/issues/3218) concerns FIFO entry kinds and inflated historical-source import counts; that import rejects the nonregular input without opening it, while this report concerns blocked JavaScript recovery acquisition and cancellation. - Open [#3197](https://github.com/morluto/rea/issues/3197) concerns an interrupted binary-target admission that settles with `UNKNOWN` instead of a typed cancellation result; this report concerns a recovery read that never settles after cancellation. - Closed completed [#3198](https://github.com/morluto/rea/issues/3198) concerns composed native MCP operations swallowing an already typed provider cancellation; this report concerns a pending filesystem open before the recovery engine starts. - Closed completed [#827](https://github.com/morluto/rea/issues/827) introduced JavaScript source recovery and its cancellation contract; it does not report the named-pipe acquisition defect. - Closed completed [#1605](https://github.com/morluto/rea/issues/1605) added recovery of related scripts together; this report selects one FIFO and requires its acquisition to reject or settle promptly.
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#1201
No description provided.