[GH-ISSUE #5416] [Bug] macOS process exit is counted as a sampling read failure #3283

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

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

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

On macOS, node scripts/rea.mjs capture-process fast.json --json can count a target's expected exit during initial identity inspection as a sampling read failure. An independent verifier reproduced this in two CLI attempts. Both CLI invocations exited 0 and captured target exit code 3 with quiesced settlement, but reported run token for PID <pid>: Error: process_not_in_snapshot, partial sampling, and aggregate truncation. The PID is redacted here.

In the first attempt, this disappearance was the only truncation cause. The second also had an independent shutdown interruption, which is a legitimate reason for partial coverage.

Reproduction

From the baseline checkout with its compiled CLI, a working native PTY backend, and Swift compiler, save this as fast.json:

{
  "executable": "node",
  "arguments": ["-e", "console.error('bye');process.exit(3)"],
  "timeout_ms": 5000,
  "settle_ms": 1500
}

Run twice:

node scripts/rea.mjs capture-process fast.json --json > capture-1.json
node scripts/rea.mjs capture-process fast.json --json > capture-2.json

The verifier ran this scenario twice using disposable home, XDG, temporary, and Swift module-cache directories. Both attempts produced the disappearance diagnostic. This is a timing-dependent race; two attempts do not establish its general frequency.

A separately executed control changed the program to console.error('bye');setTimeout(()=>process.exit(3),500). It retained two process samples without the disappearance diagnostic, but still had a separate shutdown interruption.

Expected behavior

docs/process-capture.md:433-449 separates expected process-exit races from failed observations. Shutdown counts only when a read or traversal remains unfinished.

A confirmed target disappearance during identity inspection should not itself increment sampling_failures or add incomplete-observation uncertainty. Genuine denied/failed reads and unfinished shutdown observations must remain represented. If no independent source is incomplete, this exit race alone should not make sampling partial or aggregate truncated true. Standard sampled-process limitations still apply.

Impact and scope

The observed harm is false sampling-failure accounting, aggregate truncation, and additional process-coverage uncertainty for a macOS exit race. Both fast-exit attempts reproduced the diagnostic; one isolated it as the sole truncation cause. Correct target exit and terminal observations remained available. Impact estimate: P2.

Source-derived scope: the race occurs after the initial identity guard and before/during token inspection. An already-absent first process-table row is handled without this failure. Linux has a separate ENOENT/ESRCH exemption; Linux runtime impact was not established. No comparison command or complete-equivalence outcome was tested.

NOT_ENOUGH_INFO: broader frequency and a practical workaround preserving the unmodified target were not established. Delaying the target changes its scheduling and is not an established workaround.

REA version

6.7.0, package manifest; source commit 074768901f3cdc8205b61773fb1faf6f6c7b3ca8.

Runtime and platform

Node.js v26.12.0; Darwin arm64; native PTY backend and Swift ownership helper available. NOT_ENOUGH_INFO: the verifier did not record the exact macOS or Swift version. The repository pins Node.js 24.18.0; a pinned-runtime replay was not performed.

Target and artifact context

A local Node process prints bye to stderr and exits with code 3. Evidence is the public CLI's JSON process capture. No external API or provider was contacted.

Interface

CLI

Earliest failure stage

Capture or observation

Relevant output or evidence

The independent verifier recorded these values in the first CLI result:

CLI exit status: 0
target exit: { code: 3, signal: 0, reason: "exited" }
settlement: quiesced, elapsed_ms 224, cleanup not_required
retained process samples: 0
sample limit reached: false
sampling_failures: 1
first diagnostic: run token for PID <pid>: Error: process_not_in_snapshot
sampling_partial: true
truncated: true

All raw and rendered frames were retained, normalized omitted_frames was zero, and filesystem coverage had no truncation or omissions. The result also added Process-tree sampling ended with an incomplete observation., a limitation naming the failed token read, and a matching process residual unknown. The second attempt reported the same disappearance plus shutdown interruption, with sampling_failures: 2.

The verifier ran npm run test:local -- src/process/capture/ProcessSampling.procReadCoverage.test.ts: all 23 tests passed. These mocked Linux tests do not establish correct handling of the real macOS helper race.

Typed outcome or recovery expected

Do not count confirmed expected disappearance as a failed observation. With no other failure, interrupted pass, exhausted limit, or incomplete source, retain exit code 3 and quiesced settlement while reporting sampling_failures: 0, sampling_partial: false, and truncated: false. Preserve independently justified uncertainty, ordinary sampled-tree limitations, and actual failed reads.

Analysis or suspected cause

Source-derived reasoning: src/process/capture/ProcessSampling.ts:253-290 reads the initial root identity, performs the macOS token read, then merges its diagnostic even when the subsequent identity read finds the target absent. src/process/ProcessOwnershipObservation.ts:992-1004 throws for an absent process or unavailable token. bridge/process/ProcessRunTokenReader.swift:279-298 returns process_not_in_snapshot when a helper snapshot lacks the PID without a snapshot-wide failure. The generic sampling read records that as a failure. The Linux path separately exempts ENOENT/ESRCH in src/process/capture/ProcessSamplingObservation.ts:103-116.

Additional context

Prepared with AI assistance and independently reproduced through the public CLI. The observations above come from that verifier's two captures and delayed control; this submission did not rerun them.

Before submission, upstream main at 1417bf943bf49e49b414e8a508bf28a31304be83 had no changes after the baseline in the cited sampling, ownership, helper, or contract files. Searches covered open and closed issues and PRs. Related #4322 concerns Linux cleanup token mismatches, #5081 concerns denied Linux reads, and #5194 concerns missing shutdown-interruption accounting. They do not report this macOS disappearance being counted as a failed sampling read.

NOT_ENOUGH_INFO: no last-known-good version or regression range was established.

Originally created by @coygeek on GitHub (Oct 20, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/5416 ### 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 On macOS, `node scripts/rea.mjs capture-process fast.json --json` can count a target's expected exit during initial identity inspection as a sampling read failure. An independent verifier reproduced this in two CLI attempts. Both CLI invocations exited 0 and captured target exit code 3 with quiesced settlement, but reported `run token for PID <pid>: Error: process_not_in_snapshot`, partial sampling, and aggregate truncation. The PID is redacted here. In the first attempt, this disappearance was the only truncation cause. The second also had an independent shutdown interruption, which is a legitimate reason for partial coverage. ### Reproduction From the baseline checkout with its compiled CLI, a working native PTY backend, and Swift compiler, save this as `fast.json`: ```json { "executable": "node", "arguments": ["-e", "console.error('bye');process.exit(3)"], "timeout_ms": 5000, "settle_ms": 1500 } ``` Run twice: ```sh node scripts/rea.mjs capture-process fast.json --json > capture-1.json node scripts/rea.mjs capture-process fast.json --json > capture-2.json ``` The verifier ran this scenario twice using disposable home, XDG, temporary, and Swift module-cache directories. Both attempts produced the disappearance diagnostic. This is a timing-dependent race; two attempts do not establish its general frequency. A separately executed control changed the program to `console.error('bye');setTimeout(()=>process.exit(3),500)`. It retained two process samples without the disappearance diagnostic, but still had a separate shutdown interruption. ### Expected behavior `docs/process-capture.md:433-449` separates expected process-exit races from failed observations. Shutdown counts only when a read or traversal remains unfinished. A confirmed target disappearance during identity inspection should not itself increment `sampling_failures` or add incomplete-observation uncertainty. Genuine denied/failed reads and unfinished shutdown observations must remain represented. If no independent source is incomplete, this exit race alone should not make sampling partial or aggregate `truncated` true. Standard sampled-process limitations still apply. ### Impact and scope The observed harm is false sampling-failure accounting, aggregate truncation, and additional process-coverage uncertainty for a macOS exit race. Both fast-exit attempts reproduced the diagnostic; one isolated it as the sole truncation cause. Correct target exit and terminal observations remained available. Impact estimate: P2. Source-derived scope: the race occurs after the initial identity guard and before/during token inspection. An already-absent first process-table row is handled without this failure. Linux has a separate ENOENT/ESRCH exemption; Linux runtime impact was not established. No comparison command or complete-equivalence outcome was tested. NOT_ENOUGH_INFO: broader frequency and a practical workaround preserving the unmodified target were not established. Delaying the target changes its scheduling and is not an established workaround. ### REA version 6.7.0, package manifest; source commit `074768901f3cdc8205b61773fb1faf6f6c7b3ca8`. ### Runtime and platform Node.js v26.12.0; Darwin arm64; native PTY backend and Swift ownership helper available. NOT_ENOUGH_INFO: the verifier did not record the exact macOS or Swift version. The repository pins Node.js 24.18.0; a pinned-runtime replay was not performed. ### Target and artifact context A local Node process prints `bye` to stderr and exits with code 3. Evidence is the public CLI's JSON process capture. No external API or provider was contacted. ### Interface CLI ### Earliest failure stage Capture or observation ### Relevant output or evidence The independent verifier recorded these values in the first CLI result: ```text CLI exit status: 0 target exit: { code: 3, signal: 0, reason: "exited" } settlement: quiesced, elapsed_ms 224, cleanup not_required retained process samples: 0 sample limit reached: false sampling_failures: 1 first diagnostic: run token for PID <pid>: Error: process_not_in_snapshot sampling_partial: true truncated: true ``` All raw and rendered frames were retained, normalized `omitted_frames` was zero, and filesystem coverage had no truncation or omissions. The result also added `Process-tree sampling ended with an incomplete observation.`, a limitation naming the failed token read, and a matching process residual unknown. The second attempt reported the same disappearance plus shutdown interruption, with `sampling_failures: 2`. The verifier ran `npm run test:local -- src/process/capture/ProcessSampling.procReadCoverage.test.ts`: all 23 tests passed. These mocked Linux tests do not establish correct handling of the real macOS helper race. ### Typed outcome or recovery expected Do not count confirmed expected disappearance as a failed observation. With no other failure, interrupted pass, exhausted limit, or incomplete source, retain exit code 3 and quiesced settlement while reporting `sampling_failures: 0`, `sampling_partial: false`, and `truncated: false`. Preserve independently justified uncertainty, ordinary sampled-tree limitations, and actual failed reads. ### Analysis or suspected cause Source-derived reasoning: `src/process/capture/ProcessSampling.ts:253-290` reads the initial root identity, performs the macOS token read, then merges its diagnostic even when the subsequent identity read finds the target absent. `src/process/ProcessOwnershipObservation.ts:992-1004` throws for an absent process or unavailable token. `bridge/process/ProcessRunTokenReader.swift:279-298` returns `process_not_in_snapshot` when a helper snapshot lacks the PID without a snapshot-wide failure. The generic sampling read records that as a failure. The Linux path separately exempts ENOENT/ESRCH in `src/process/capture/ProcessSamplingObservation.ts:103-116`. ### Additional context Prepared with AI assistance and independently reproduced through the public CLI. The observations above come from that verifier's two captures and delayed control; this submission did not rerun them. Before submission, upstream `main` at `1417bf943bf49e49b414e8a508bf28a31304be83` had no changes after the baseline in the cited sampling, ownership, helper, or contract files. Searches covered open and closed issues and PRs. Related #4322 concerns Linux cleanup token mismatches, #5081 concerns denied Linux reads, and #5194 concerns missing shutdown-interruption accounting. They do not report this macOS disappearance being counted as a failed sampling read. NOT_ENOUGH_INFO: no last-known-good version or regression range was established.
Author
Owner

@coygeek commented on GitHub (Oct 24, 2026):

Checked the reported fix on main at d87e73f2db.

readSamplingSource distinguishes ProcessRunTokenReadError availability absent as process-exited; samplingReadObservation produces no sampling failure for that fact. Unavailable tokens still count as failures. Regression simulates native Darwin initial identity absence and permission failure separately and asserts zero failures/complete sampling only for absence.

Implementation: ProcessSamplingObservation.ts, ProcessSamplingObservation.ts.

The relevant regression checks in ProcessSampling.procReadCoverage.test.ts passed in the focused test run on this revision. This closure is based on the current source and those checks; provider fixtures do not establish a real-engine run. Closing as completed on main.

<!-- gh-comment-id:6380762868 --> @coygeek commented on GitHub (Oct 24, 2026): Checked the reported fix on `main` at d87e73f2db50be72d33a4b3c7199af35518b441c. readSamplingSource distinguishes ProcessRunTokenReadError availability absent as process-exited; samplingReadObservation produces no sampling failure for that fact. Unavailable tokens still count as failures. Regression simulates native Darwin initial identity absence and permission failure separately and asserts zero failures/complete sampling only for absence. Implementation: [ProcessSamplingObservation.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/process/capture/ProcessSamplingObservation.ts#L74), [ProcessSamplingObservation.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/process/capture/ProcessSamplingObservation.ts#L106). The relevant regression checks in [ProcessSampling.procReadCoverage.test.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/process/capture/ProcessSampling.procReadCoverage.test.ts) passed in the focused test run on this revision. This closure is based on the current source and those checks; provider fixtures do not establish a real-engine run. Closing as completed on main.
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#3283
No description provided.