[GH-ISSUE #5292] [Bug] Owned command loses observed exit failure to cancellation before classification #3226

Closed
opened 2026-10-31 00:38:04 +00:00 by bsakaguchi · 1 comment
Owner

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

Before submitting

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

Area

Process capture or runtime observation

Observed behavior

At source revision e84e17e30f7de41ad9ac801eda235574fa5d4dad, runOwnedCommand can report cancellation after its supervisor has already recorded a nonzero child exit and complete stderr, if cancellation arrives before the awaiting owner continuation classifies that outcome. A real owned Node child exiting 7 with authored physical failure on stderr was returned as OwnedCommandFailure.reason === "cancelled"; the snapshot still contained exit 7 and the stderr.

Reproduction

The final boundary case launches process.execPath with a child that writes stdout and authored physical failure to stderr, then exits 7. A Writable sink stalls its finalization. The test spy waits for the real ProviderProcessSupervisor.waitForOutputClose() to return true, pauses the owner's continuation, aborts the caller, then releases the continuation. This exercises a real process exit and close; it only delays owner classification.

I copied the final tests/boundary/process/ownedCommand.test.ts into a disposable detached worktree at the exact source revision above and ran the full file using Node 24.18.0:

PATH=/Users/will/.nvm/versions/node/v24.18.0/bin:$PATH npm run test:focused -- tests/boundary/process/ownedCommand.test.ts

The baseline run produced 1 failed | 25 passed. The new test received reason: "cancelled" while its snapshot contained exit 7 and the authored stderr. Full local output is retained at .cache/codebase-audit/owned5292-final-baseline-red.log. The disposable worktree was removed afterward; a process-table check found no remaining Node child matching the authored failure marker.

On the local owner patch, the same final regression passes with reason: "process", exit 7, stderr, and the exact cancellation reason separately retained. It also asserts the abort-induced sink rejection does not attach a processError or set collectionFailure; the sink is destroyed and refused cleanup can be retried. An active child aborted before it exits remains cancelled. Pinned focused owner suites pass 32/32; the persisted integrated green run is .cache/codebase-audit/owned5292-consumer-validation.log.

Expected behavior

A failure recorded by the owner before interruption remains primary through classification and cleanup. Preserve its output and exit details, while retaining any later caller cancellation reason separately. Cancellation that causes the child to terminate remains cancellation.

Impact and scope

Confirmed at the shared owned-command boundary with a controlled, source-owned Node child. Frequency with ordinary external providers and effects on public CLI/MCP calls: NOT_ENOUGH_INFO; no external provider or public workflow was run.

REA version

Source revision e84e17e30f7de41ad9ac801eda235574fa5d4dad; package 6.7.0.

Runtime and platform

Node 24.18.0; macOS 25.3 arm64.

Target and artifact context

A harmless local Node child launched and owned by REA. No external target, provider, or network service.

Interface

Other

Earliest failure stage

Cleanup, cancellation, or lifecycle

Relevant output or evidence

The final baseline regression result had reason: "cancelled" and a snapshot with exitCode: 7 and stderr authored physical failure. The patched run keeps the physical failure and exact late cancellation fact separate. The real active-child cancellation control remains cancelled.

Typed outcome or recovery expected

Keep OwnedCommandFailure.reason === "process", the exit and diagnostics, and the exact late caller reason as a separate cancellation fact. Preserve cancellation when interruption precedes the child exit.

Analysis or suspected cause

Source-derived: ProviderProcessSupervisor emits a typed exit diagnostic while recording the exit. OwnedCommand previously checked deadline.interruption before classifying the post-close snapshot, allowing a later abort to supersede an earlier observed failure. The local uncommitted change records process-failure and output-limit observations at the diagnostic boundary; it does not classify accepted nonzero exits as failures.

Related reports: #5279 and #5284 concern consumers replacing an already classified owner failure; this report concerns loss inside the owner before classification. #4005 covers cancellation during later process-capture finalization. #4227 tracks the broader lifecycle audit.

Originally created by @morluto on GitHub (Oct 19, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/5292 ### 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 Process capture or runtime observation ### Observed behavior At source revision `e84e17e30f7de41ad9ac801eda235574fa5d4dad`, `runOwnedCommand` can report cancellation after its supervisor has already recorded a nonzero child exit and complete stderr, if cancellation arrives before the awaiting owner continuation classifies that outcome. A real owned Node child exiting 7 with `authored physical failure` on stderr was returned as `OwnedCommandFailure.reason === "cancelled"`; the snapshot still contained exit 7 and the stderr. ### Reproduction The final boundary case launches `process.execPath` with a child that writes stdout and `authored physical failure` to stderr, then exits 7. A Writable sink stalls its finalization. The test spy waits for the real `ProviderProcessSupervisor.waitForOutputClose()` to return `true`, pauses the owner's continuation, aborts the caller, then releases the continuation. This exercises a real process exit and close; it only delays owner classification. I copied the final `tests/boundary/process/ownedCommand.test.ts` into a disposable detached worktree at the exact source revision above and ran the full file using Node 24.18.0: ```sh PATH=/Users/will/.nvm/versions/node/v24.18.0/bin:$PATH npm run test:focused -- tests/boundary/process/ownedCommand.test.ts ``` The baseline run produced `1 failed | 25 passed`. The new test received `reason: "cancelled"` while its snapshot contained exit 7 and the authored stderr. Full local output is retained at `.cache/codebase-audit/owned5292-final-baseline-red.log`. The disposable worktree was removed afterward; a process-table check found no remaining Node child matching the authored failure marker. On the local owner patch, the same final regression passes with `reason: "process"`, exit 7, stderr, and the exact cancellation reason separately retained. It also asserts the abort-induced sink rejection does not attach a `processError` or set `collectionFailure`; the sink is destroyed and refused cleanup can be retried. An active child aborted before it exits remains cancelled. Pinned focused owner suites pass 32/32; the persisted integrated green run is `.cache/codebase-audit/owned5292-consumer-validation.log`. ### Expected behavior A failure recorded by the owner before interruption remains primary through classification and cleanup. Preserve its output and exit details, while retaining any later caller cancellation reason separately. Cancellation that causes the child to terminate remains cancellation. ### Impact and scope Confirmed at the shared owned-command boundary with a controlled, source-owned Node child. Frequency with ordinary external providers and effects on public CLI/MCP calls: NOT_ENOUGH_INFO; no external provider or public workflow was run. ### REA version Source revision `e84e17e30f7de41ad9ac801eda235574fa5d4dad`; package 6.7.0. ### Runtime and platform Node 24.18.0; macOS 25.3 arm64. ### Target and artifact context A harmless local Node child launched and owned by REA. No external target, provider, or network service. ### Interface Other ### Earliest failure stage Cleanup, cancellation, or lifecycle ### Relevant output or evidence The final baseline regression result had `reason: "cancelled"` and a snapshot with `exitCode: 7` and stderr `authored physical failure`. The patched run keeps the physical failure and exact late cancellation fact separate. The real active-child cancellation control remains cancelled. ### Typed outcome or recovery expected Keep `OwnedCommandFailure.reason === "process"`, the exit and diagnostics, and the exact late caller reason as a separate cancellation fact. Preserve cancellation when interruption precedes the child exit. ### Analysis or suspected cause Source-derived: `ProviderProcessSupervisor` emits a typed `exit` diagnostic while recording the exit. `OwnedCommand` previously checked `deadline.interruption` before classifying the post-close snapshot, allowing a later abort to supersede an earlier observed failure. The local uncommitted change records process-failure and output-limit observations at the diagnostic boundary; it does not classify accepted nonzero exits as failures. Related reports: #5279 and #5284 concern consumers replacing an already classified owner failure; this report concerns loss inside the owner before classification. #4005 covers cancellation during later process-capture finalization. #4227 tracks the broader lifecycle audit.
bsakaguchi 2026-10-31 00:38:04 +00:00
  • closed this issue
  • added the
    triage
    bug
    labels
Author
Owner

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

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

<!-- gh-comment-id:6300573984 --> @morluto commented on GitHub (Oct 20, 2026): Closing as completed: verified during REA Bot open-bug audit that the described owned-command exit/cancellation classification 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#3226
No description provided.