[GH-ISSUE #1746] [Bug] Cancelled MCP evidence import still commits into the live session #587

Closed
opened 2026-10-15 18:26:26 +00:00 by bsakaguchi · 0 comments
Owner

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

Before submitting

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

Area

MCP or agent integration

Observed behavior

Cancelling an in-flight import_evidence_bundle request ends the client's request, but REA continues reading the file and commits the imported records into the same live session.

This was reproduced through real stdio MCP, with a valid local regular JSON file and a fresh empty session. The probe waited until the server had opened the selected file before cancelling; it also recorded the SDK sending notifications/cancelled for the import request ID. After the read finished, an export from that session contained the entire imported bundle.

Reproduction

  1. Build the pinned source with npm ci and npm run build:cached.

  2. Through node scripts/rea.mjs mcp, create a small valid bundle using record_unknown, then export_evidence_bundle. The tested bundle contains one mutation Evidence record and one Unknown.

  3. Make a second regular JSON file containing 128 MiB of leading ASCII spaces followed by that bundle's JSON. Leading JSON whitespace is valid; this exercises the streaming reader without creating a large semantic object.

  4. Start a fresh stdio MCP server and verify its exported bundle has zero records and zero Unknowns.

  5. Using the MCP SDK client, start:

    const controller = new AbortController();
    const request = client.callTool(
      { name: "import_evidence_bundle", arguments: { path: paddedBundlePath } },
      { signal: controller.signal, timeout: 45000 },
    );
    
  6. On Linux, poll /proc/<server-pid>/fd until an open descriptor resolves to paddedBundlePath. Immediately abort the controller and await the rejected request. Confirm the transport sent notifications/cancelled with the import's request ID.

  7. Wait until that descriptor closes, export from the same session, and inspect the record and Unknown counts. Ping the server to verify the transport remains usable.

Expected behavior

Cancellation observed during the asynchronous file-read phase should stop the read, release the owned file handle, and leave the session unchanged. Check cancellation again before committing the import.

Impact and scope

A caller can receive a cancellation error while the session changes afterward. The file read also continues consuming resources. This is unexpected post-cancellation state mutation, rather than a digest or bundle-authentication failure. The successful uncancelled import remains valid, and the transport remains usable.

REA version

Source revision 21650455a9c90a26e725c3505a3c17054cc4516c.

Runtime and platform

Linux x86_64 VPS; Node.js v24.18.0; MCP SDK 2.3.1; stdio transport. Runs were serialized on one CPU with a 512 MiB server V8 old-space limit.

Target and artifact context

Local Evidence bundle JSON with one Evidence record, one Unknown, and a 128 MiB whitespace prefix. No native provider, GUI, target execution, or model credentials are involved.

Interface

MCP

Earliest failure stage

Cleanup, cancellation, or lifecycle

Relevant output or evidence

cancelled run:
  file opened:             5.8 ms
  client cancellation:    6.6 ms
  notification:           notifications/cancelled, requestId 2
  client error:           SdkError: Error: cancel confirmed in-flight import
  file closed:            511.6 ms
  session before:         0 records, 0 Unknowns
  session afterward:      1 record, 1 Unknown
  full exported bundle:   equals the import fixture
  ping / owned cleanup:   passed

uncancelled control:
  request completed:      457.6 ms
  session afterward:      1 record, 1 Unknown

Independent repeats reproduced the same post-cancellation state change. A final repeat used a newly generated bundle from this same source revision via public record_unknown and export_evidence_bundle, with full mutation parameters, and again confirmed the matched cancellation notification, complete post-cancel import, ping, and owned-process exit. Timings are observations from this host, not portable performance thresholds.

Typed outcome or recovery expected

Return cancellation and avoid committing this import; retain the pre-request session state. A subsequent valid import should still succeed.

Analysis or suspected cause

Source-derived cause: the import handler omits the request context and invokes the reader without a signal. The immutable bundle reader and JSON wrapper do not propagate cancellation. The underlying readJsonInputFile already supports a signal. Export passes context.mcpReq.signal to its writer.

Additional context

Suggested regression: cancel after a production reader dispatch checkpoint, assert the file handle is released and the session snapshot is unchanged, then verify a subsequent uncancelled import and ping. Preserve existing authentication, malformed-input, regular-file ownership, and capacity checks. The probe deliberately cancels during asynchronous reading; it does not claim mid-turn interruption of synchronous validation.

Originally created by @N0zoM1z0 on GitHub (Oct 11, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/1746 ### 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 MCP or agent integration ### Observed behavior Cancelling an in-flight `import_evidence_bundle` request ends the client's request, but REA continues reading the file and commits the imported records into the same live session. This was reproduced through real stdio MCP, with a valid local regular JSON file and a fresh empty session. The probe waited until the server had opened the selected file before cancelling; it also recorded the SDK sending `notifications/cancelled` for the import request ID. After the read finished, an export from that session contained the entire imported bundle. ### Reproduction 1. Build the pinned source with `npm ci` and `npm run build:cached`. 2. Through `node scripts/rea.mjs mcp`, create a small valid bundle using `record_unknown`, then `export_evidence_bundle`. The tested bundle contains one mutation Evidence record and one Unknown. 3. Make a second regular JSON file containing 128 MiB of leading ASCII spaces followed by that bundle's JSON. Leading JSON whitespace is valid; this exercises the streaming reader without creating a large semantic object. 4. Start a fresh stdio MCP server and verify its exported bundle has zero records and zero Unknowns. 5. Using the MCP SDK client, start: ```js const controller = new AbortController(); const request = client.callTool( { name: "import_evidence_bundle", arguments: { path: paddedBundlePath } }, { signal: controller.signal, timeout: 45000 }, ); ``` 6. On Linux, poll `/proc/<server-pid>/fd` until an open descriptor resolves to `paddedBundlePath`. Immediately abort the controller and await the rejected request. Confirm the transport sent `notifications/cancelled` with the import's request ID. 7. Wait until that descriptor closes, export from the same session, and inspect the record and Unknown counts. Ping the server to verify the transport remains usable. ### Expected behavior Cancellation observed during the asynchronous file-read phase should stop the read, release the owned file handle, and leave the session unchanged. Check cancellation again before committing the import. ### Impact and scope A caller can receive a cancellation error while the session changes afterward. The file read also continues consuming resources. This is unexpected post-cancellation state mutation, rather than a digest or bundle-authentication failure. The successful uncancelled import remains valid, and the transport remains usable. ### REA version Source revision `21650455a9c90a26e725c3505a3c17054cc4516c`. ### Runtime and platform Linux x86_64 VPS; Node.js v24.18.0; MCP SDK 2.3.1; stdio transport. Runs were serialized on one CPU with a 512 MiB server V8 old-space limit. ### Target and artifact context Local Evidence bundle JSON with one Evidence record, one Unknown, and a 128 MiB whitespace prefix. No native provider, GUI, target execution, or model credentials are involved. ### Interface MCP ### Earliest failure stage Cleanup, cancellation, or lifecycle ### Relevant output or evidence ```text cancelled run: file opened: 5.8 ms client cancellation: 6.6 ms notification: notifications/cancelled, requestId 2 client error: SdkError: Error: cancel confirmed in-flight import file closed: 511.6 ms session before: 0 records, 0 Unknowns session afterward: 1 record, 1 Unknown full exported bundle: equals the import fixture ping / owned cleanup: passed uncancelled control: request completed: 457.6 ms session afterward: 1 record, 1 Unknown ``` Independent repeats reproduced the same post-cancellation state change. A final repeat used a newly generated bundle from this same source revision via public `record_unknown` and `export_evidence_bundle`, with full mutation parameters, and again confirmed the matched cancellation notification, complete post-cancel import, ping, and owned-process exit. Timings are observations from this host, not portable performance thresholds. ### Typed outcome or recovery expected Return cancellation and avoid committing this import; retain the pre-request session state. A subsequent valid import should still succeed. ### Analysis or suspected cause Source-derived cause: the [import handler](https://github.com/morluto/rea/blob/21650455a9c90a26e725c3505a3c17054cc4516c/src/server/registerSessionRecordTools.ts#L89-L119) omits the request context and invokes the reader without a signal. [The immutable bundle reader](https://github.com/morluto/rea/blob/21650455a9c90a26e725c3505a3c17054cc4516c/src/application/EvidenceBundleFiles.ts#L39-L56) and [JSON wrapper](https://github.com/morluto/rea/blob/21650455a9c90a26e725c3505a3c17054cc4516c/src/application/JsonFiles.ts#L35-L43) do not propagate cancellation. The underlying `readJsonInputFile` already supports a signal. Export passes `context.mcpReq.signal` to its writer. ### Additional context Suggested regression: cancel after a production reader dispatch checkpoint, assert the file handle is released and the session snapshot is unchanged, then verify a subsequent uncancelled import and ping. Preserve existing authentication, malformed-input, regular-file ownership, and capacity checks. The probe deliberately cancels during asynchronous reading; it does not claim mid-turn interruption of synchronous validation.
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#587
No description provided.