[GH-ISSUE #6131] [Bug] Cancelling a shared Kuna assembly-index wait drops the caller's abort reason #3757

Closed
opened 2026-10-31 00:43:44 +00:00 by bsakaguchi · 0 comments
Owner

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

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

Cancelling a request while KunaProjectReader waits on its shared assembly index returns AnalysisCancelledError with cause === undefined, although the request signal still holds the exact object passed to AbortController.abort(reason).

Executed through the production Kuna provider client for procedure_assembly({procedure: "main"}), analyze_function({procedure: "main"}), and address_name({address: "0x3010"}). All three lost the reason. The repository's simulated Kuna executable produced the export; a controlled assembly stream held indexing pending. No real Kuna executable or CLI/MCP transport reproduction was performed.

Reproduction

  1. Install the locked dependencies with npm ci and build with npm run build:cached at the revision below, using the pinned Node.js and npm versions.

  2. Create a temporary focused test beside kunaReaderCancellation.test.ts, reusing that file's setup and first parameterized test (existing setup and cases). It creates a production client with the simulated Kuna executable, warms the project export through procedure_pseudo_code, and intercepts only that export's assembly read with a PassThrough stream.

  3. After the test observes exactly one held assembly stream, cancel the selected request with an explicit object:

    const reason = Object.freeze({ requested: "reader-stop", operation });
    controller.abort(reason);
    const result = await cancelled;
    if (result.ok) throw new Error("Expected cancellation");
    expect(result.error._tag).toBe("AnalysisCancelledError");
    expect(controller.signal.reason).toBe(reason);
    
  4. Keep the existing controls: the shared stream is still active, health succeeds, releasing the stream lets the peer procedure_assembly({procedure: "parse"}) succeed, and only one stream was created. Then assert expect(result.error.cause).toBe(reason). Keep the existing finally block that releases the stream, joins both requests, and checks successful client close.

  5. Executed the three cases in tests/boundary/providers/kuna/followupCancellationReason.test.ts with:

    node node_modules/vitest/vitest.mjs run tests/boundary/providers/kuna/followupCancellationReason.test.ts --maxWorkers=1
    

One run, three reason-identity assertion failures. Each failed with expected undefined to be { requested: 'reader-stop', ... }; the peer, health, and close controls succeeded. The temporary test was removed afterward.

Expected behavior

The returned typed cancellation should preserve the exact caller abort reason as cause, while keeping the shared index alive for other readers. The lifecycle contract explicitly requires preserving caller abort reasons (contract); the Kuna provider documents independently cancellable index waits (provider behavior).

Impact and scope

Confirmed diagnostic loss at the production provider-client boundary for these three requests when cancellation occurs during assembly indexing. Cancellation still completes, and peer work and cleanup succeeded in the reproduction. Consumers inspecting AnalysisCancelledError.cause cannot recover the caller's reason from this result. The caller can retain its own signal separately, but the typed error violates its documented contract. Exact arbitrary-reason projection through CLI/MCP and behavior with a real Kuna executable were not tested.

REA version

01788bac86

Runtime and platform

Node.js v24.18.0, npm 11.16.0, macOS 27.0.1 arm64; Vitest 5.0.3 from the lockfile. Simulated Kuna executable from the repository's provider tests.

Target and artifact context

Synthetic ELF x86_64 target descriptor and simulated project export from kunaSessionFixture.ts, including program.asm, function entries main and parse, and a data-label lookup at 0x3010. This is provider-boundary coverage, not real ELF analysis.

Interface

Other

Earliest failure stage

Cleanup, cancellation, or lifecycle

Relevant output or evidence

The focused test printed the following directly from the returned provider results:

{"producer":"kuna-reader","operation":"procedure_assembly","tag":"AnalysisCancelledError","causeAbsent":true,"signalRetainsReason":true}
{"producer":"kuna-reader","operation":"analyze_function","tag":"AnalysisCancelledError","causeAbsent":true,"signalRetainsReason":true}
{"producer":"kuna-reader","operation":"address_name","tag":"AnalysisCancelledError","causeAbsent":true,"signalRetainsReason":true}

All three final cause identity assertions received undefined. Installation and runtime build completed successfully before the focused test. No full test suite was run.

Source-derived reachability: address_name forwards the request signal to reader.dataName (call site); assembly and dossier reads forward it to reader.assembly (call sites). These are supported operations listed in the Kuna provider guide.

Typed outcome or recovery expected

AnalysisCancelledError whose cause is the same value as the caller's signal.reason; shared scan ownership, peer completion, and successful cleanup should remain intact.

Analysis or suspected cause

Source-derived: KunaProjectReader.#index converts the ABORTED sentinel into new AnalysisCancelledError(operation) without options (producer). The caller's signal is available there, and KunaSessionClient.waitFor already uses { cause: signal?.reason } for the analogous session wait (comparison). The omission is sufficient to explain the observed loss.

Additional context

Closed, unmerged PR #4711 proposed the identical KunaProjectReader.#index change as part of #4700; current main still contains the reasonless constructor. #4700 was closed after the shared-query ownership repair, while this report reproduces the separate assembly-index reader path.

Related cancellation-reason reports in other producers: #5935, #5944, #5959, #5960, #5964, #5985, #5987, #6100, #6101, #6110, and #6113.

Originally created by @coygeek on GitHub (Oct 21, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6131 ### 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 Cancelling a request while `KunaProjectReader` waits on its shared assembly index returns `AnalysisCancelledError` with `cause === undefined`, although the request signal still holds the exact object passed to `AbortController.abort(reason)`. Executed through the production Kuna provider client for `procedure_assembly({procedure: "main"})`, `analyze_function({procedure: "main"})`, and `address_name({address: "0x3010"})`. All three lost the reason. The repository's simulated Kuna executable produced the export; a controlled assembly stream held indexing pending. No real Kuna executable or CLI/MCP transport reproduction was performed. ### Reproduction 1. Install the locked dependencies with `npm ci` and build with `npm run build:cached` at the revision below, using the pinned Node.js and npm versions. 2. Create a temporary focused test beside `kunaReaderCancellation.test.ts`, reusing that file's setup and first parameterized test ([existing setup and cases](https://github.com/morluto/rea/blob/01788bac863ac786bfbb648cad69b11e23b40a89/tests/boundary/providers/kuna/kunaReaderCancellation.test.ts#L1-L104)). It creates a production client with the simulated Kuna executable, warms the project export through `procedure_pseudo_code`, and intercepts only that export's assembly read with a `PassThrough` stream. 3. After the test observes exactly one held assembly stream, cancel the selected request with an explicit object: ```ts const reason = Object.freeze({ requested: "reader-stop", operation }); controller.abort(reason); const result = await cancelled; if (result.ok) throw new Error("Expected cancellation"); expect(result.error._tag).toBe("AnalysisCancelledError"); expect(controller.signal.reason).toBe(reason); ``` 4. Keep the existing controls: the shared stream is still active, `health` succeeds, releasing the stream lets the peer `procedure_assembly({procedure: "parse"})` succeed, and only one stream was created. Then assert `expect(result.error.cause).toBe(reason)`. Keep the existing `finally` block that releases the stream, joins both requests, and checks successful client close. 5. Executed the three cases in `tests/boundary/providers/kuna/followupCancellationReason.test.ts` with: ```sh node node_modules/vitest/vitest.mjs run tests/boundary/providers/kuna/followupCancellationReason.test.ts --maxWorkers=1 ``` One run, three reason-identity assertion failures. Each failed with `expected undefined to be { requested: 'reader-stop', ... }`; the peer, health, and close controls succeeded. The temporary test was removed afterward. ### Expected behavior The returned typed cancellation should preserve the exact caller abort reason as `cause`, while keeping the shared index alive for other readers. The lifecycle contract explicitly requires preserving caller abort reasons ([contract](https://github.com/morluto/rea/blob/01788bac863ac786bfbb648cad69b11e23b40a89/docs/tool-design.md#L101-L111)); the Kuna provider documents independently cancellable index waits ([provider behavior](https://github.com/morluto/rea/blob/01788bac863ac786bfbb648cad69b11e23b40a89/docs/kuna-provider.md#L67-L69)). ### Impact and scope Confirmed diagnostic loss at the production provider-client boundary for these three requests when cancellation occurs during assembly indexing. Cancellation still completes, and peer work and cleanup succeeded in the reproduction. Consumers inspecting `AnalysisCancelledError.cause` cannot recover the caller's reason from this result. The caller can retain its own signal separately, but the typed error violates its documented contract. Exact arbitrary-reason projection through CLI/MCP and behavior with a real Kuna executable were not tested. ### REA version 01788bac863ac786bfbb648cad69b11e23b40a89 ### Runtime and platform Node.js v24.18.0, npm 11.16.0, macOS 27.0.1 arm64; Vitest 5.0.3 from the lockfile. Simulated Kuna executable from the repository's provider tests. ### Target and artifact context Synthetic ELF x86_64 target descriptor and simulated project export from `kunaSessionFixture.ts`, including `program.asm`, function entries `main` and `parse`, and a data-label lookup at `0x3010`. This is provider-boundary coverage, not real ELF analysis. ### Interface Other ### Earliest failure stage Cleanup, cancellation, or lifecycle ### Relevant output or evidence The focused test printed the following directly from the returned provider results: ```json {"producer":"kuna-reader","operation":"procedure_assembly","tag":"AnalysisCancelledError","causeAbsent":true,"signalRetainsReason":true} {"producer":"kuna-reader","operation":"analyze_function","tag":"AnalysisCancelledError","causeAbsent":true,"signalRetainsReason":true} {"producer":"kuna-reader","operation":"address_name","tag":"AnalysisCancelledError","causeAbsent":true,"signalRetainsReason":true} ``` All three final `cause` identity assertions received `undefined`. Installation and runtime build completed successfully before the focused test. No full test suite was run. Source-derived reachability: `address_name` forwards the request signal to `reader.dataName` ([call site](https://github.com/morluto/rea/blob/01788bac863ac786bfbb648cad69b11e23b40a89/src/kuna/KunaSessionClient.ts#L930-L943)); assembly and dossier reads forward it to `reader.assembly` ([call sites](https://github.com/morluto/rea/blob/01788bac863ac786bfbb648cad69b11e23b40a89/src/kuna/KunaSessionClient.ts#L1161-L1197)). These are supported operations listed in the [Kuna provider guide](https://github.com/morluto/rea/blob/01788bac863ac786bfbb648cad69b11e23b40a89/docs/kuna-provider.md#L36-L48). ### Typed outcome or recovery expected `AnalysisCancelledError` whose `cause` is the same value as the caller's `signal.reason`; shared scan ownership, peer completion, and successful cleanup should remain intact. ### Analysis or suspected cause Source-derived: `KunaProjectReader.#index` converts the `ABORTED` sentinel into `new AnalysisCancelledError(operation)` without options ([producer](https://github.com/morluto/rea/blob/01788bac863ac786bfbb648cad69b11e23b40a89/src/kuna/KunaProjectReader.ts#L172-L193)). The caller's signal is available there, and `KunaSessionClient.waitFor` already uses `{ cause: signal?.reason }` for the analogous session wait ([comparison](https://github.com/morluto/rea/blob/01788bac863ac786bfbb648cad69b11e23b40a89/src/kuna/KunaSessionClient.ts#L144-L153)). The omission is sufficient to explain the observed loss. ### Additional context Closed, unmerged PR #4711 proposed the identical `KunaProjectReader.#index` change as part of #4700; current main still contains the reasonless constructor. #4700 was closed after the shared-query ownership repair, while this report reproduces the separate assembly-index reader path. Related cancellation-reason reports in other producers: #5935, #5944, #5959, #5960, #5964, #5985, #5987, #6100, #6101, #6110, and #6113.
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#3757
No description provided.