[GH-ISSUE #5714] [Bug] Hopper cancellation errors discard the caller abort reason #3464

Closed
opened 2026-10-31 00:40:45 +00:00 by bsakaguchi · 1 comment
Owner

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

Before submitting

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

Area

Native analysis or decompilation

Observed behavior

The compiled HopperRequestQueue returns HopperCancelledError without cause after its caller aborts with an opaque object. Cancellation remains the reported outcome, but the exact caller reason is lost. This was reproduced at the production queue boundary using an inert send callback; no provider or target process was launched.

Reproduction

From a built checkout of 84c89d1107cda017509bc3cd851573d073f9906d, run:

node --input-type=module <<'JS'
import { HopperRequestQueue } from './dist/hopper/HopperRequestQueue.js';
const reason = { source: 'caller', opaque: 7 };
const controller = new AbortController();
const queue = new HopperRequestQueue(() => {});
const pending = queue.run(1, 'echo', {}, { signal: controller.signal });
controller.abort(reason);
const result = await pending;
if (result.ok) throw new Error('Expected cancellation');
console.log(JSON.stringify({
  tag: result.error._tag,
  same_reason: result.error.cause === reason,
  has_cause: Object.hasOwn(result.error, 'cause'),
}));
queue.accept(1, { ok: true, value: null });
JS

Executed once through the compiled API. Independently, a new source regression against the unchanged production files failed its exact-object-cause assertion; the remaining 14 queue tests passed. A separate constructor regression also showed explicit undefined and an omitted cause are conflated.

Expected behavior

result.error.cause === reason, while cancellation classification, late-reply ownership and cleanup behavior remain intact. The lifecycle contract requires the caller's abort reason as the typed cancellation error's cause.

Impact and scope

Observed internal typed-error provenance loss. The queue caller cannot recover its supplied object from the result. NOT_ENOUGH_INFO: public CLI/MCP delivery of opaque causes, real Hopper occurrence, other platforms and production frequency were not tested. No data loss or target execution was observed.

REA version

Source 84c89d1107cda017509bc3cd851573d073f9906d, package 6.7.0. The compiled probe used the same production code plus an unrelated test-only commit.

Runtime and platform

Node.js v24.18.0; macOS 26.3 arm64. No Hopper/Ghidra runtime involved.

Target and artifact context

No target. One inert queue request with a recording-free send callback.

Interface

Other: internal production Hopper request-queue API.

Earliest failure stage

Cleanup, cancellation, or lifecycle

Relevant output or evidence

Observed compiled result:

{"tag":"HopperCancelledError","same_reason":false,"has_cause":false}

Source regression: expected the exact {source: 'caller', opaque: 7} object, received undefined at result.error.cause.

Typed outcome or recovery expected

Keep HopperCancelledError and retain the exact reason in its cause. Preserve explicit omission separately from an explicitly supplied undefined; native AbortController.abort(undefined) itself substitutes a DOMException, so the latter was tested directly on the constructor.

Analysis or suspected cause

Source-derived: queue admission, cancellation observer and pre-send cancellation construct the error without the signal reason. Similar conversion sites exist in Hopper client startup, socket connection and launcher preparation. The constructor additionally tests value equality with undefined rather than argument omission.

Additional context

AI-assisted report. #4921 / PR #4966 concern Linux helper shutdown and stale locks; #5429 concerns precedence between an observed physical startup failure and later cancellation. #5670, #4700 and #4686 cover similar reason loss in other boundaries, not this Hopper queue conversion. No introducing commit or last known-good version was established.

Originally created by @morluto on GitHub (Oct 20, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/5714 ### 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 Native analysis or decompilation ### Observed behavior The compiled `HopperRequestQueue` returns `HopperCancelledError` without `cause` after its caller aborts with an opaque object. Cancellation remains the reported outcome, but the exact caller reason is lost. This was reproduced at the production queue boundary using an inert send callback; no provider or target process was launched. ### Reproduction From a built checkout of `84c89d1107cda017509bc3cd851573d073f9906d`, run: ```sh node --input-type=module <<'JS' import { HopperRequestQueue } from './dist/hopper/HopperRequestQueue.js'; const reason = { source: 'caller', opaque: 7 }; const controller = new AbortController(); const queue = new HopperRequestQueue(() => {}); const pending = queue.run(1, 'echo', {}, { signal: controller.signal }); controller.abort(reason); const result = await pending; if (result.ok) throw new Error('Expected cancellation'); console.log(JSON.stringify({ tag: result.error._tag, same_reason: result.error.cause === reason, has_cause: Object.hasOwn(result.error, 'cause'), })); queue.accept(1, { ok: true, value: null }); JS ``` Executed once through the compiled API. Independently, a new source regression against the unchanged production files failed its exact-object-cause assertion; the remaining 14 queue tests passed. A separate constructor regression also showed explicit `undefined` and an omitted cause are conflated. ### Expected behavior `result.error.cause === reason`, while cancellation classification, late-reply ownership and cleanup behavior remain intact. The [lifecycle contract](https://github.com/morluto/rea/blob/84c89d1107cda017509bc3cd851573d073f9906d/docs/tool-design.md#L106) requires the caller's abort reason as the typed cancellation error's cause. ### Impact and scope Observed internal typed-error provenance loss. The queue caller cannot recover its supplied object from the result. NOT_ENOUGH_INFO: public CLI/MCP delivery of opaque causes, real Hopper occurrence, other platforms and production frequency were not tested. No data loss or target execution was observed. ### REA version Source `84c89d1107cda017509bc3cd851573d073f9906d`, package 6.7.0. The compiled probe used the same production code plus an unrelated test-only commit. ### Runtime and platform Node.js v24.18.0; macOS 26.3 arm64. No Hopper/Ghidra runtime involved. ### Target and artifact context No target. One inert queue request with a recording-free send callback. ### Interface Other: internal production Hopper request-queue API. ### Earliest failure stage Cleanup, cancellation, or lifecycle ### Relevant output or evidence Observed compiled result: ```json {"tag":"HopperCancelledError","same_reason":false,"has_cause":false} ``` Source regression: expected the exact `{source: 'caller', opaque: 7}` object, received `undefined` at `result.error.cause`. ### Typed outcome or recovery expected Keep `HopperCancelledError` and retain the exact reason in its cause. Preserve explicit omission separately from an explicitly supplied `undefined`; native `AbortController.abort(undefined)` itself substitutes a DOMException, so the latter was tested directly on the constructor. ### Analysis or suspected cause Source-derived: [queue admission](https://github.com/morluto/rea/blob/84c89d1107cda017509bc3cd851573d073f9906d/src/hopper/HopperRequestQueue.ts#L79), cancellation observer and pre-send cancellation construct the error without the signal reason. Similar conversion sites exist in Hopper client startup, socket connection and launcher preparation. The [constructor](https://github.com/morluto/rea/blob/84c89d1107cda017509bc3cd851573d073f9906d/src/domain/hopperErrors.ts#L63) additionally tests value equality with undefined rather than argument omission. ### Additional context AI-assisted report. #4921 / PR #4966 concern Linux helper shutdown and stale locks; #5429 concerns precedence between an observed physical startup failure and later cancellation. #5670, #4700 and #4686 cover similar reason loss in other boundaries, not this Hopper queue conversion. No introducing commit or last known-good version was established.
bsakaguchi 2026-10-31 00:40:45 +00:00
  • closed this issue
  • added the
    bug
    triage
    labels
Author
Owner

@matbrgz commented on GitHub (Oct 21, 2026):

Cross-referencing with canonical issue #5345 (inspect_native_target cancellation replaces native tool failure with execution_failure) and sibling cancellation reports #5364, #5373, #5385, #5641, #5670.

Consolidated under Cluster BO (Cancellation Error Projection & Diagnostic Erasure) in tracker #4330.

<!-- gh-comment-id:6304921540 --> @matbrgz commented on GitHub (Oct 21, 2026): Cross-referencing with canonical issue #5345 (`inspect_native_target cancellation replaces native tool failure with execution_failure`) and sibling cancellation reports #5364, #5373, #5385, #5641, #5670. Consolidated under **Cluster BO (Cancellation Error Projection & Diagnostic Erasure)** in tracker #4330.
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#3464
No description provided.