[GH-ISSUE #3197] [Bug] SIGINT during target file admission reports UNKNOWN instead of cancellation #1135

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

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

CLI

Observed behavior

rea inspect and rea analyze return {"code":"UNKNOWN","message":"This operation was aborted"} when SIGINT arrives during the first file-descriptor stat in target admission. Each process exits 1. The same signal during the next stat returns code: "cancelled", category: "cancelled", retryable: true, and details: {"operation":"resolve_binary_target","cleanup":"complete"}, also with exit status 1.

The independent verification executed both commands at both checkpoints with one installed SIGINT listener per process. The interrupted operation stops, but the first-stat response loses the cancellation category, retryability, remediation, and cleanup metadata. This controlled timing does not establish how often natural interrupts land in that window.

Reproduction

  1. Start from a clean checkout at 58300885207133422af235e0f2b10658cf958b03 with the repository's pinned development toolchain. Install dependencies and build, then create a disposable fixture and isolate configuration. The verification used prepared dependencies and the built runtime; installation and build below are clean-checkout prerequisites.

    npm ci
    npm run build:cached
    mkdir -p .repro-admission/home/config .repro-admission/home/cache .repro-admission/home/data
    export HOME="$PWD/.repro-admission/home"
    export XDG_CONFIG_HOME="$HOME/config"
    export XDG_CACHE_HOME="$HOME/cache"
    export XDG_DATA_HOME="$HOME/data"
    printf 'sample-bytes' > .repro-admission/sample.bin
    export REPRO_SAMPLE="$PWD/.repro-admission/sample.bin"
    
  2. Create this Node preload. It calls the real file-descriptor stat, delivers SIGINT to its own CLI process at the selected checkpoint, and preserves the stat result.

    cat > .repro-admission/admission-hook.mjs <<'EOF'
    import { open } from 'node:fs/promises';
    const initial = await open(process.env.REPRO_SAMPLE, 'r');
    const prototype = Object.getPrototypeOf(initial);
    await initial.close();
    const original = prototype.stat;
    let count = 0;
    prototype.stat = async function (...args) {
      const stats = await original.apply(this, args);
      count++;
      if (count === Number(process.env.REPRO_ABORT_STAT)) {
        process.stderr.write(`admission-probe: stat=${count} SIGINT-listeners=${process.listenerCount('SIGINT')}\n`);
        process.kill(process.pid, 'SIGINT');
        await new Promise(resolve => setTimeout(resolve, 20));
      }
      return stats;
    };
    EOF
    
  3. Run both supported commands with cancellation at the first and second stat, recording each exit status. The first stat reports UNKNOWN; the second stat reports cancelled.

    for command in inspect analyze; do
      for checkpoint in 1 2; do
        REPRO_ABORT_STAT="$checkpoint" node --import ./.repro-admission/admission-hook.mjs scripts/rea.mjs "$command" "$REPRO_SAMPLE" --format json
        printf 'Exit status: %s\n' "$?"
      done
    done
    
  4. Disable cancellation for the format-rejection control. The sample is deliberately not a recognized executable; this command returns the ordinary typed target_unavailable error with category unsupported_target and constraint file_format_rejected.

    REPRO_ABORT_STAT=0 node --import ./.repro-admission/admission-hook.mjs scripts/rea.mjs inspect "$REPRO_SAMPLE" --format json
    printf 'Exit status: %s\n' "$?"
    

Cancellation occurs before format validation. No Hopper, Ghidra, IDA, or other analysis engine is needed.

Expected behavior

SIGINT during readable local target resolution should produce the canonical cancellation result regardless of which admission checkpoint receives it. AGENTS.md, Architecture and Cleanup, requires expected failures to use the tagged error algebra and Result. docs/tool-design.md:68 and docs/tool-design.md:86 require cancellation reporting and verification through affected consumers. The resolver declares typed cancellation at src/application/BinaryTargetResolver.ts:74-79 and creates AnalysisCancelledError("resolve_binary_target") at lines 272-274. Its later-stat checkpoint already returns the expected public cancellation classification.

Impact and scope

This timing window affects structured error classification in the supported inspect and analyze CLI commands. Callers lose useful cancellation and recovery fields even though the operation stops. The observations do not establish data loss, target mutation, a blocked uncancelled analysis workflow, an incorrect signal exit status, or successful provider analysis. MCP cancellation reaches the same resolver according to source inspection, but no MCP wire cancellation or exact client result was executed.

No practical CLI workaround that restores these fields was verified. A caller separately tracking its requested cancellation is a possible workaround, not a demonstrated one.

REA version

6.4.0 (main at 5830088520)

Runtime and platform

Node.js v26.10.0; macOS 27.0.1; arm64. Verification used the prepared built runtime and started no provider engine.

Target and artifact context

A readable local regular file containing only sample-bytes, named sample.bin. Its bytes deliberately fail executable-format validation in the uncancelled control. The timing preload preserves the real stat result and changes only when SIGINT arrives.

Interface

CLI

Earliest failure stage

Cleanup, cancellation, or lifecycle

Relevant output or evidence

First stat, inspect and analyze:
admission-probe: stat=1 SIGINT-listeners=1
{"code":"UNKNOWN","message":"This operation was aborted"}
exit=1

Second stat, inspect and analyze, selected response fields:
code=cancelled category=cancelled retryable=true
operation=resolve_binary_target cleanup=complete
exit=1

Cancellation disabled, inspect, selected response fields:
code=target_unavailable category=unsupported_target
reason=unsupported binary format constraint=file_format_rejected

Typed outcome or recovery expected

Repeat first-stat cancellation for both commands and require the same canonical cancellation classification and recovery fields as the second-stat control, including code: "cancelled", category: "cancelled", retryable: true, the target-resolution operation, and cleanup metadata. Preserve the later-stat cancellation control and the ordinary unsupported-format result when cancellation is disabled. The interrupted operation must continue to release its owned resources. This restoration check does not require changing the observed exit status.

Analysis or suspected cause

Source-derived reasoning at the reported baseline: src/filesystem/RegularFile.ts:84-85 checks cancellation immediately after the first descriptor stat and throws signal.reason. Lines 99-105 close the owned descriptor and rethrow the reason. src/application/BinaryTargetResolver.ts:224-230 recognizes typed REA errors but rethrows this abort reason after filesystem classification returns undefined. Its typed cancellation checkpoint at lines 272-274 is therefore bypassed.

src/application/DirectAnalysisCleanup.ts:16-33 closes the session and preserves the rejection. src/cliLogging.ts:126-148 projects only AnalysisError and rethrows other causes, after which the CLI reports the unexpected rejection as UNKNOWN. src/domain/analysisErrorProjection.ts:201 and lines 453-470 define the expected cancelled projection and operation/cleanup metadata.

Source-derived reachability runs through src/cli/coreBinaryCommands.ts:140-155, src/application/DirectAnalysis.ts:102-121,283-303,474-493, src/application/binary/BinarySession.ts:213-229,260-272, and src/application/binary/BinarySessionOpen.ts:69-94 before provider selection. Independent internal resolver probes also rejected default and custom Error abort reasons by object identity during admission, while a pre-aborted controller returned typed AnalysisCancelledError. Those probes supplement the executed CLI evidence.

Additional context

Impact assessment: P2, supported. The demonstrated cancellation window loses consequential structured recovery information in two supported commands. The operation still stops, and the verification does not show severe harm or a core workflow blocked for all inputs. Natural race frequency and a practical workaround remain unverified.

Related: #3198 reports a separate failure in the same area; the two have independent fixes.

Originally created by @coygeek on GitHub (Oct 14, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3197 ### 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 CLI ### Observed behavior `rea inspect` and `rea analyze` return `{"code":"UNKNOWN","message":"This operation was aborted"}` when SIGINT arrives during the first file-descriptor stat in target admission. Each process exits 1. The same signal during the next stat returns `code: "cancelled"`, `category: "cancelled"`, `retryable: true`, and `details: {"operation":"resolve_binary_target","cleanup":"complete"}`, also with exit status 1. The independent verification executed both commands at both checkpoints with one installed SIGINT listener per process. The interrupted operation stops, but the first-stat response loses the cancellation category, retryability, remediation, and cleanup metadata. This controlled timing does not establish how often natural interrupts land in that window. ### Reproduction 1. Start from a clean checkout at `58300885207133422af235e0f2b10658cf958b03` with the repository's pinned development toolchain. Install dependencies and build, then create a disposable fixture and isolate configuration. The verification used prepared dependencies and the built runtime; installation and build below are clean-checkout prerequisites. ```sh npm ci npm run build:cached mkdir -p .repro-admission/home/config .repro-admission/home/cache .repro-admission/home/data export HOME="$PWD/.repro-admission/home" export XDG_CONFIG_HOME="$HOME/config" export XDG_CACHE_HOME="$HOME/cache" export XDG_DATA_HOME="$HOME/data" printf 'sample-bytes' > .repro-admission/sample.bin export REPRO_SAMPLE="$PWD/.repro-admission/sample.bin" ``` 2. Create this Node preload. It calls the real file-descriptor stat, delivers SIGINT to its own CLI process at the selected checkpoint, and preserves the stat result. ```sh cat > .repro-admission/admission-hook.mjs <<'EOF' import { open } from 'node:fs/promises'; const initial = await open(process.env.REPRO_SAMPLE, 'r'); const prototype = Object.getPrototypeOf(initial); await initial.close(); const original = prototype.stat; let count = 0; prototype.stat = async function (...args) { const stats = await original.apply(this, args); count++; if (count === Number(process.env.REPRO_ABORT_STAT)) { process.stderr.write(`admission-probe: stat=${count} SIGINT-listeners=${process.listenerCount('SIGINT')}\n`); process.kill(process.pid, 'SIGINT'); await new Promise(resolve => setTimeout(resolve, 20)); } return stats; }; EOF ``` 3. Run both supported commands with cancellation at the first and second stat, recording each exit status. The first stat reports `UNKNOWN`; the second stat reports `cancelled`. ```sh for command in inspect analyze; do for checkpoint in 1 2; do REPRO_ABORT_STAT="$checkpoint" node --import ./.repro-admission/admission-hook.mjs scripts/rea.mjs "$command" "$REPRO_SAMPLE" --format json printf 'Exit status: %s\n' "$?" done done ``` 4. Disable cancellation for the format-rejection control. The sample is deliberately not a recognized executable; this command returns the ordinary typed `target_unavailable` error with category `unsupported_target` and constraint `file_format_rejected`. ```sh REPRO_ABORT_STAT=0 node --import ./.repro-admission/admission-hook.mjs scripts/rea.mjs inspect "$REPRO_SAMPLE" --format json printf 'Exit status: %s\n' "$?" ``` Cancellation occurs before format validation. No Hopper, Ghidra, IDA, or other analysis engine is needed. ### Expected behavior SIGINT during readable local target resolution should produce the canonical cancellation result regardless of which admission checkpoint receives it. `AGENTS.md`, Architecture and Cleanup, requires expected failures to use the tagged error algebra and Result. `docs/tool-design.md:68` and `docs/tool-design.md:86` require cancellation reporting and verification through affected consumers. The resolver declares typed cancellation at `src/application/BinaryTargetResolver.ts:74-79` and creates `AnalysisCancelledError("resolve_binary_target")` at lines 272-274. Its later-stat checkpoint already returns the expected public cancellation classification. ### Impact and scope This timing window affects structured error classification in the supported `inspect` and `analyze` CLI commands. Callers lose useful cancellation and recovery fields even though the operation stops. The observations do not establish data loss, target mutation, a blocked uncancelled analysis workflow, an incorrect signal exit status, or successful provider analysis. MCP cancellation reaches the same resolver according to source inspection, but no MCP wire cancellation or exact client result was executed. No practical CLI workaround that restores these fields was verified. A caller separately tracking its requested cancellation is a possible workaround, not a demonstrated one. ### REA version 6.4.0 (main at 58300885207133422af235e0f2b10658cf958b03) ### Runtime and platform Node.js v26.10.0; macOS 27.0.1; arm64. Verification used the prepared built runtime and started no provider engine. ### Target and artifact context A readable local regular file containing only `sample-bytes`, named `sample.bin`. Its bytes deliberately fail executable-format validation in the uncancelled control. The timing preload preserves the real stat result and changes only when SIGINT arrives. ### Interface CLI ### Earliest failure stage Cleanup, cancellation, or lifecycle ### Relevant output or evidence ```text First stat, inspect and analyze: admission-probe: stat=1 SIGINT-listeners=1 {"code":"UNKNOWN","message":"This operation was aborted"} exit=1 Second stat, inspect and analyze, selected response fields: code=cancelled category=cancelled retryable=true operation=resolve_binary_target cleanup=complete exit=1 Cancellation disabled, inspect, selected response fields: code=target_unavailable category=unsupported_target reason=unsupported binary format constraint=file_format_rejected ``` ### Typed outcome or recovery expected Repeat first-stat cancellation for both commands and require the same canonical cancellation classification and recovery fields as the second-stat control, including `code: "cancelled"`, `category: "cancelled"`, `retryable: true`, the target-resolution operation, and cleanup metadata. Preserve the later-stat cancellation control and the ordinary unsupported-format result when cancellation is disabled. The interrupted operation must continue to release its owned resources. This restoration check does not require changing the observed exit status. ### Analysis or suspected cause Source-derived reasoning at the reported baseline: `src/filesystem/RegularFile.ts:84-85` checks cancellation immediately after the first descriptor stat and throws `signal.reason`. Lines 99-105 close the owned descriptor and rethrow the reason. `src/application/BinaryTargetResolver.ts:224-230` recognizes typed REA errors but rethrows this abort reason after filesystem classification returns undefined. Its typed cancellation checkpoint at lines 272-274 is therefore bypassed. `src/application/DirectAnalysisCleanup.ts:16-33` closes the session and preserves the rejection. `src/cliLogging.ts:126-148` projects only `AnalysisError` and rethrows other causes, after which the CLI reports the unexpected rejection as `UNKNOWN`. `src/domain/analysisErrorProjection.ts:201` and lines 453-470 define the expected `cancelled` projection and operation/cleanup metadata. Source-derived reachability runs through `src/cli/coreBinaryCommands.ts:140-155`, `src/application/DirectAnalysis.ts:102-121,283-303,474-493`, `src/application/binary/BinarySession.ts:213-229,260-272`, and `src/application/binary/BinarySessionOpen.ts:69-94` before provider selection. Independent internal resolver probes also rejected default and custom Error abort reasons by object identity during admission, while a pre-aborted controller returned typed `AnalysisCancelledError`. Those probes supplement the executed CLI evidence. ### Additional context Impact assessment: P2, supported. The demonstrated cancellation window loses consequential structured recovery information in two supported commands. The operation still stops, and the verification does not show severe harm or a core workflow blocked for all inputs. Natural race frequency and a practical workaround remain unverified. Related: #3198 reports a separate failure in the same area; the two have independent fixes.
bsakaguchi 2026-10-15 18:31:24 +00:00
  • closed this issue
  • added the
    bug
    label
Author
Owner

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

Resolved on main by 2daeff4b2.

Owned target-admission cancellation is normalized from the exact aborted signal reason instead of becoming UNKNOWN.

Rechecked on main b8510ee00 in the current focused run: 20 test files passed, 225 tests passed and one platform-dependent test skipped. Relevant regression coverage: tests/process-global/binaryTargetAdmissionCancellation.test.ts.

<!-- gh-comment-id:6174986687 --> @morluto commented on GitHub (Oct 14, 2026): Resolved on main by 2daeff4b2. Owned target-admission cancellation is normalized from the exact aborted signal reason instead of becoming UNKNOWN. Rechecked on main b8510ee00 in the current focused run: 20 test files passed, 225 tests passed and one platform-dependent test skipped. Relevant regression coverage: tests/process-global/binaryTargetAdmissionCancellation.test.ts.
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#1135
No description provided.