[GH-ISSUE #5202] [Bug] Darwin reader close can remove its root before admitted queries finish #3190

Closed
opened 2026-10-31 00:37:41 +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/5202

Before submitting

  • Searched open and closed issues for related reports.
  • Removed credentials and sensitive target data.

Area / interface / failure stage

Process capture or runtime observation; internal production reader boundary; cleanup, cancellation, or lifecycle.

Observed behavior

The Darwin ownership reader's close() can remove its executable directory before an admitted read() or identities() command finishes. Both cases reproduced in deterministic source-boundary regressions against ba866a26ecbe179a717e746cbc0d1605ae616ad5.

Reproduction

Use the production reader with an explicitly held execFileOutput helper result, real temporary compiler output, and real directory removal observed through a filesystem wrapper. After helper entry, call close(), yield to the event loop, release valid helper JSON, then await both operations. Recorded ordering is root removal before helper finalization for both operations.

Executed on pinned Node 24.18.0: npm run test:run -- --project adapters src/process/DarwinProcessRunTokenReader.finalization.test.ts. Both regressions fail on that ordering. This is controlled boundary evidence, not a natural native-helper reproduction.

Expected behavior

Close admission, retain the root for admitted commands, and drain their owned lifetime before removing it. See ownership and completion invariants.

Impact and scope

An already launched POSIX helper can outlive root removal; if removal wins before spawn, an admitted call can fail because its executable disappeared. This consequence is source-derived. NOT_ENOUGH_INFO: no ordinary in-repository production call site closing during a read was established; normal consumers await reads. Native concurrent close, Linux, and Windows runtime behavior were not reproduced.

REA version / runtime / target

Source ba866a26ecbe179a717e746cbc0d1605ae616ad5; macOS arm64; Node 24.18.0. Controlled compiler/helper boundary with valid token and identity responses.

Relevant evidence and analysis

The two observed event sequences place root-removed before helper-finalized. DarwinProcessRunTokenReader.ts tracks compilation during close, but does not track admitted helper commands. Proposed correction: one helper-command owner shared by token and identity reads, with supervised stop and retained pending commands drained before root cleanup. Related: #5199 concerns cancellation returning before command exit.

Originally created by @morluto on GitHub (Oct 19, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/5202 ### Before submitting - [x] Searched open and closed issues for related reports. - [x] Removed credentials and sensitive target data. ### Area / interface / failure stage Process capture or runtime observation; internal production reader boundary; cleanup, cancellation, or lifecycle. ### Observed behavior The Darwin ownership reader's `close()` can remove its executable directory before an admitted `read()` or `identities()` command finishes. Both cases reproduced in deterministic source-boundary regressions against `ba866a26ecbe179a717e746cbc0d1605ae616ad5`. ### Reproduction Use the production reader with an explicitly held `execFileOutput` helper result, real temporary compiler output, and real directory removal observed through a filesystem wrapper. After helper entry, call `close()`, yield to the event loop, release valid helper JSON, then await both operations. Recorded ordering is root removal before helper finalization for both operations. Executed on pinned Node 24.18.0: `npm run test:run -- --project adapters src/process/DarwinProcessRunTokenReader.finalization.test.ts`. Both regressions fail on that ordering. This is controlled boundary evidence, not a natural native-helper reproduction. ### Expected behavior Close admission, retain the root for admitted commands, and drain their owned lifetime before removing it. See [ownership and completion invariants](https://github.com/morluto/rea/blob/ba866a26ecbe179a717e746cbc0d1605ae616ad5/docs/tool-design.md#own-the-lifecycle). ### Impact and scope An already launched POSIX helper can outlive root removal; if removal wins before spawn, an admitted call can fail because its executable disappeared. This consequence is source-derived. NOT_ENOUGH_INFO: no ordinary in-repository production call site closing during a read was established; normal consumers await reads. Native concurrent close, Linux, and Windows runtime behavior were not reproduced. ### REA version / runtime / target Source `ba866a26ecbe179a717e746cbc0d1605ae616ad5`; macOS arm64; Node 24.18.0. Controlled compiler/helper boundary with valid token and identity responses. ### Relevant evidence and analysis The two observed event sequences place `root-removed` before `helper-finalized`. [DarwinProcessRunTokenReader.ts](https://github.com/morluto/rea/blob/ba866a26ecbe179a717e746cbc0d1605ae616ad5/src/process/DarwinProcessRunTokenReader.ts#L696) tracks compilation during close, but does not track admitted helper commands. Proposed correction: one helper-command owner shared by token and identity reads, with supervised stop and retained pending commands drained before root cleanup. Related: #5199 concerns cancellation returning before command exit.
bsakaguchi 2026-10-31 00:37:41 +00:00
  • closed this issue
  • added the
    triage
    bug
    labels
Author
Owner

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

Fixed on main in ca71e91d23.

The root cause was treating an abort rejection as command completion. The sampler and ownership-query callers now use the existing supervised stop contract, which waits for exit and escalates after its grace period. The adjacent Darwin reader had a separate ownership gap: close tracked compilation but omitted admitted queries. Token and identity queries now share one command owner; close rejects new command admission, drains existing commands, and only then removes the helper root. Failed root removal retains its exact owner for retry without rerunning queries.

Validation: 66 relevant tests passed across focused runs, including native macOS helper controls and CLI/MCP cancellation; the final 8-test finalization subset passed after adding mixed query outcomes and failed-cleanup retry. The unchanged shared command owner also passed its 30 cancellation/timeout controls. npm run check, npm run docs:check, and discovery of all 1,637 test files passed. All seven original counterexamples were observed failing before their producing fixes (five delayed command cases and two held-helper close cases).

Scope: delayed SIGTERM uses real source-owned POSIX subprocesses on macOS. Concurrent reader close uses explicit production-boundary gates and real directory removal; normal native macOS helper reads also passed. The FreeBSD route was exercised with an explicit host selection on macOS, not a real BSD kernel. Native Windows and current real Linux behavior remain unverified.

Three completed real CLI captures retained identical terminal/normal-exit/verified-cleanup oracles. Repeated medians against the earlier e8da baseline: elapsed 1.949→1.921 s; family CPU 1.839→1.784 s; sampled family peak RSS 515.0→514.1 MB; estimated full-reply cl100k tokens 2063→2062.5 (7,642 bytes both). These are small, non-alternating samples with warm filesystem caches, not a speedup claim; actual agent task tokens, true cold runs, and MCP discovery costs were not measured. Cancellation intentionally waits for physical completion.

The Node abort behavior was researched through Context7 and DeepWiki and verified against pinned Node 24.18.0 source. No related cleanup remains in this command-owner slice.

<!-- gh-comment-id:6262174727 --> @morluto commented on GitHub (Oct 19, 2026): Fixed on `main` in ca71e91d23672646af917ede8ae82c7fea395341. The root cause was treating an abort rejection as command completion. The sampler and ownership-query callers now use the existing supervised stop contract, which waits for exit and escalates after its grace period. The adjacent Darwin reader had a separate ownership gap: close tracked compilation but omitted admitted queries. Token and identity queries now share one command owner; close rejects new command admission, drains existing commands, and only then removes the helper root. Failed root removal retains its exact owner for retry without rerunning queries. Validation: 66 relevant tests passed across focused runs, including native macOS helper controls and CLI/MCP cancellation; the final 8-test finalization subset passed after adding mixed query outcomes and failed-cleanup retry. The unchanged shared command owner also passed its 30 cancellation/timeout controls. `npm run check`, `npm run docs:check`, and discovery of all 1,637 test files passed. All seven original counterexamples were observed failing before their producing fixes (five delayed command cases and two held-helper close cases). Scope: delayed SIGTERM uses real source-owned POSIX subprocesses on macOS. Concurrent reader close uses explicit production-boundary gates and real directory removal; normal native macOS helper reads also passed. The FreeBSD route was exercised with an explicit host selection on macOS, not a real BSD kernel. Native Windows and current real Linux behavior remain unverified. Three completed real CLI captures retained identical terminal/normal-exit/verified-cleanup oracles. Repeated medians against the earlier e8da baseline: elapsed 1.949→1.921 s; family CPU 1.839→1.784 s; sampled family peak RSS 515.0→514.1 MB; estimated full-reply cl100k tokens 2063→2062.5 (7,642 bytes both). These are small, non-alternating samples with warm filesystem caches, not a speedup claim; actual agent task tokens, true cold runs, and MCP discovery costs were not measured. Cancellation intentionally waits for physical completion. The Node abort behavior was researched through Context7 and DeepWiki and verified against [pinned Node 24.18.0 source](https://github.com/nodejs/node/blob/v24.18.0/lib/child_process.js#L705-L714). No related cleanup remains in this command-owner slice.
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#3190
No description provided.