[GH-ISSUE #5287] [Bug] Native command cancellation loses its exact reason and masks late resolver I/O failures #3223

Closed
opened 2026-10-31 00:38:02 +00:00 by bsakaguchi · 3 comments
Owner

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

Before submitting

  • Searched open and closed issues and PRs for native command cancellation and resolver failures. Open #5279 reports the analogous Pcap projection bug; #4227 is a general lifecycle audit. Neither covers this native macOS command boundary or xcrun tool resolution.
  • No credentials or sensitive target data included.

Area / interface / failure stage

Native analysis or decompilation; Other (production command boundary); cleanup, cancellation, or lifecycle.

Observed behavior

On the source baseline, native cancellation loses its exact opaque caller reason before reaching AnalysisCancelledError. OwnedCommandFailure already records cancellation separately, but captureProcess drops that fact and translateCommandFailure emits a cancellation error without the reason as its cause.

Tool resolution has a related classification problem: if caller cancellation arrives while realpath fails with a physical I/O error, resolveXcrunTool labels that failure cancelled based only on the ambient signal. The physical I/O failure is thereby hidden.

Reproduction

On e84e17e30f7de41ad9ac801eda235574fa5d4dad, Node v25.1.0, macOS 26.3 arm64, ran the new focused native boundary regressions with both native source files restored to baseline:

npm run test:focused -- tests/boundary/providers/native/nativeProvider.test.ts tests/boundary/providers/native/nativeCommandResolver.test.ts

The owned-process test launches an authored Node child through XcrunCommandRunner, runOwnedCommand, and NativeMacOSProvider; a ready marker is written after stdout/stderr writes, then the test aborts with an opaque object reason. Baseline result lost AnalysisCancelledError.cause. Resolver tests deterministically reject a pending realpath with EACCES after abort and cancel an actual /dev/zero hash read; baseline misclassified the realpath error as cancellation and did not retain the hash AbortError's exact reason. Both regressions reproduced in that baseline run.

Expected behavior

Preserve the producer-owned exact cancellation fact independently from the physical error. Classify a realpath/hash failure as cancellation only when the caught error establishes that the signal aborted the operation; keep a late physical I/O error primary.

Impact and scope

Observed loss of cancellation provenance and masking of a resolver I/O error in the native macOS provider path. Native Xcode tools, real target binaries, compiled CLI/MCP delivery, other Node versions, and non-macOS platforms were not tested (NOT_ENOUGH_INFO).

Relevant output or evidence

Source: src/process/OwnedCommand.ts retains the explicit cancellation fact; baseline src/native/CommandRunner.ts dropped it in captureProcess and reclassified any realpath/hash catch as cancelled when signal.aborted; src/native/NativeMacOSProvider.ts projected cancellation without the exact reason.

Baseline focused run failed the new exact-cause and resolver classification assertions. On the working change, the same focused tests pass 52/52; npm run check:fast, npm run verify:test-discovery (1675 files exactly once), and git diff --check pass. This report does not claim Xcode validation.

Additional context

Frequency in ordinary Xcode use and downstream user impact are unknown. The local working change is not integrated; this issue remains open for maintainer triage.

Originally created by @morluto on GitHub (Oct 19, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/5287 ### Before submitting - [x] Searched open and closed issues and PRs for native command cancellation and resolver failures. Open #5279 reports the analogous Pcap projection bug; #4227 is a general lifecycle audit. Neither covers this native macOS command boundary or xcrun tool resolution. - [x] No credentials or sensitive target data included. ### Area / interface / failure stage Native analysis or decompilation; Other (production command boundary); cleanup, cancellation, or lifecycle. ### Observed behavior On the source baseline, native cancellation loses its exact opaque caller reason before reaching `AnalysisCancelledError`. `OwnedCommandFailure` already records cancellation separately, but `captureProcess` drops that fact and `translateCommandFailure` emits a cancellation error without the reason as its cause. Tool resolution has a related classification problem: if caller cancellation arrives while `realpath` fails with a physical I/O error, `resolveXcrunTool` labels that failure `cancelled` based only on the ambient signal. The physical I/O failure is thereby hidden. ### Reproduction On `e84e17e30f7de41ad9ac801eda235574fa5d4dad`, Node v25.1.0, macOS 26.3 arm64, ran the new focused native boundary regressions with both native source files restored to baseline: ```sh npm run test:focused -- tests/boundary/providers/native/nativeProvider.test.ts tests/boundary/providers/native/nativeCommandResolver.test.ts ``` The owned-process test launches an authored Node child through `XcrunCommandRunner`, `runOwnedCommand`, and `NativeMacOSProvider`; a ready marker is written after stdout/stderr writes, then the test aborts with an opaque object reason. Baseline result lost `AnalysisCancelledError.cause`. Resolver tests deterministically reject a pending realpath with `EACCES` after abort and cancel an actual `/dev/zero` hash read; baseline misclassified the realpath error as cancellation and did not retain the hash AbortError's exact reason. Both regressions reproduced in that baseline run. ### Expected behavior Preserve the producer-owned exact cancellation fact independently from the physical error. Classify a realpath/hash failure as cancellation only when the caught error establishes that the signal aborted the operation; keep a late physical I/O error primary. ### Impact and scope Observed loss of cancellation provenance and masking of a resolver I/O error in the native macOS provider path. Native Xcode tools, real target binaries, compiled CLI/MCP delivery, other Node versions, and non-macOS platforms were not tested (`NOT_ENOUGH_INFO`). ### Relevant output or evidence Source: `src/process/OwnedCommand.ts` retains the explicit cancellation fact; baseline `src/native/CommandRunner.ts` dropped it in `captureProcess` and reclassified any realpath/hash catch as cancelled when `signal.aborted`; `src/native/NativeMacOSProvider.ts` projected cancellation without the exact reason. Baseline focused run failed the new exact-cause and resolver classification assertions. On the working change, the same focused tests pass 52/52; `npm run check:fast`, `npm run verify:test-discovery` (1675 files exactly once), and `git diff --check` pass. This report does not claim Xcode validation. ### Additional context Frequency in ordinary Xcode use and downstream user impact are unknown. The local working change is not integrated; this issue remains open for maintainer triage.
bsakaguchi 2026-10-31 00:38:02 +00:00
  • closed this issue
  • added the
    triage
    bug
    labels
Author
Owner

@akushonkamen commented on GitHub (Oct 19, 2026):

Claiming this — I'll pick it up and work on preserving the exact cancellation reason and correcting the classification of late resolver I/O failures. Will follow up with a branch shortly.

<!-- gh-comment-id:6276132967 --> @akushonkamen commented on GitHub (Oct 19, 2026): Claiming this — I'll pick it up and work on preserving the exact cancellation reason and correcting the classification of late resolver I/O failures. Will follow up with a branch shortly.
Author
Owner

@akushonkamen commented on GitHub (Oct 19, 2026):

I picked this up; a fix is now available on the fork branch fix/5287-cancel-reason-transparency (commit 01d1a2d5f, based on current main b4938aed5):

  • captureProcess now carries the producer-owned cancellation fact from OwnedCommandFailure into NativeCommandFailure, and translateCommandFailure projects it as the cause of AnalysisCancelledError, so the exact opaque caller reason survives the provider boundary.
  • resolveXcrunTool now classifies a realpath/hash failure as cancelled only when the caught error itself establishes the abort (AbortError/ABORT_ERR, keeping its exact reason); a late physical I/O failure such as EACCES stays primary and is reported as io.

Verification (macOS arm64, Node 24):

  • New focused regressions in tests/boundary/providers/native/nativeProvider.test.ts and a new tests/boundary/providers/native/nativeCommandResolver.test.ts reproduce all three scenarios: with the fix stashed they fail 3/51 (baseline behavior), with the fix applied all 51 pass.
  • npx vitest run --project boundary: 5460 passed; the failing set is byte-identical to a clean main run on this machine (pre-existing, unrelated subsystems), so no new failures are introduced.
  • npm run check:fast (typecheck + lint) passes.

No PR was opened this round per current workflow constraints; the branch is ready for review.

<!-- gh-comment-id:6276795322 --> @akushonkamen commented on GitHub (Oct 19, 2026): I picked this up; a fix is now available on the fork branch [`fix/5287-cancel-reason-transparency`](https://github.com/akushonkamen/rea/tree/fix/5287-cancel-reason-transparency) (commit 01d1a2d5f, based on current main b4938aed5): - `captureProcess` now carries the producer-owned cancellation fact from `OwnedCommandFailure` into `NativeCommandFailure`, and `translateCommandFailure` projects it as the `cause` of `AnalysisCancelledError`, so the exact opaque caller reason survives the provider boundary. - `resolveXcrunTool` now classifies a `realpath`/`hash` failure as cancelled only when the caught error itself establishes the abort (`AbortError`/`ABORT_ERR`, keeping its exact reason); a late physical I/O failure such as `EACCES` stays primary and is reported as `io`. Verification (macOS arm64, Node 24): - New focused regressions in `tests/boundary/providers/native/nativeProvider.test.ts` and a new `tests/boundary/providers/native/nativeCommandResolver.test.ts` reproduce all three scenarios: with the fix stashed they fail 3/51 (baseline behavior), with the fix applied all 51 pass. - `npx vitest run --project boundary`: 5460 passed; the failing set is byte-identical to a clean `main` run on this machine (pre-existing, unrelated subsystems), so no new failures are introduced. - `npm run check:fast` (typecheck + lint) passes. No PR was opened this round per current workflow constraints; the branch is ready for review.
Author
Owner

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

Closing as completed: verified during REA Bot open-bug audit that the described native-command cancellation/exact-reason fix is already on current main with regression coverage. No further PR needed for this report.

<!-- gh-comment-id:6300573789 --> @morluto commented on GitHub (Oct 20, 2026): Closing as completed: verified during REA Bot open-bug audit that the described native-command cancellation/exact-reason fix is already on current main with regression coverage. No further PR needed for this report.
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#3223
No description provided.