[GH-ISSUE #8032] [Bug] Process capture reports cleanup_outcome "not_required" after its cleanup killed a detached descendant #4661

Open
opened 2026-10-31 00:52:08 +00:00 by bsakaguchi · 0 comments
Owner

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

Before submitting

  • I searched the open and closed issues for a related report (cleanup_outcome not_required, process capture cleanup, detached descendant, setsid, settlement, escaped process group). Related but different: #4005 (closed, SIGINT during capture cleanup), #6445 (closed, exit code for a signal-terminated target).
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Process capture or runtime observation

Observed behavior

A captured command forks a child that calls setsid() and keeps working after the parent exits. REA's owned-process cleanup finds the child through its run token and kills it with SIGKILL. The terminal progress line for that run says so:

... disposition=exited owned_process_group=cleaned terminal_renderer=cleaned temporary_root=cleaned

The capture Evidence doesn't reflect that:

  • settlement is {"state": "quiesced", "cleanup_outcome": "not_required"}.
  • cleanup.owned_process_group is "verified".
  • process_samples is empty, and residual_unknowns only has the generic "Process trees are sampled and may omit short-lived descendants."

So the Evidence says no cleanup was needed, while REA ended a live process that the target started. The target's observable behavior also changes: run directly, the child finishes and writes its log. Under capture, the child is killed within about half a second, so filesystem_effects shows a log that was cut short. Nothing in the Evidence explains that.

In ProcessHarness.ts and ProcessCaptureLifecycle.ts, cleanup_outcome is derived only from capture.settlement.state (quiesced gives not_required). The signaled flag that the ownership cleanup returns isn't used.

compare-process-captures doesn't overclaim here. It reports process: unknown against a run of /bin/echo parent-exit.

Reproduction

I used a self-written probe in a Linux x64 container (--network none) on Ubuntu 24.04. I ran it on rea-agents@6.8.0 (CLI and MCP) and on main at 45cc93596 (MCP). The results were the same.

/* orphan.c: the parent exits at once; a setsid child logs ticks for 5 s and logs any catchable signal */
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
static FILE *log_file;
static void on_sig(int s) { fprintf(log_file, "signal %d\n", s); fflush(log_file); _exit(0); }
int main(void) {
    if (fork() == 0) {
        setsid();
        log_file = fopen("orphan.log", "w");
        for (int s = 1; s < 32; s++) if (s != SIGKILL && s != SIGSTOP) signal(s, on_sig);
        fprintf(log_file, "child pid=%d ppid=%d\n", getpid(), getppid()); fflush(log_file);
        for (int i = 0; i < 10; i++) { usleep(500000); fprintf(log_file, "tick %d\n", i); fflush(log_file); }
        fputs("finished\n", log_file); fclose(log_file);
        return 0;
    }
    printf("parent-exit\n");
    return 0;
}
{"executable": "/p/orphan", "working_directory": "/tmp/pc", "filesystem_observation_paths": ["/tmp/pc"], "timeout_ms": 5000}

rea capture-process scenario.json --json, or the same object through MCP capture_process_scenario.

  • Direct run: orphan.log gets 10 ticks and finished.
  • Under capture: orphan.log stays at the single child pid=... ppid=1 line, even after waiting 6 seconds. No catchable signal is logged, which points to SIGKILL. The settlement is the one shown above.

Expected behavior

When cleanup signals a process, the Evidence should say so. For example, cleanup_outcome: "cleaned", plus a residual unknown or limitation saying that a token-owned descendant was still running after the root exited and was terminated, so later filesystem and output observations may differ from an uncaptured run. Killing the descendant may well be the intended design. The issue is only that the Evidence reports it as not needed.

Impact and scope

This affects Linux process capture of targets that start detached workers, daemons or background writers. Someone reading the Evidence sees a clean, quiesced run and can't tell that REA ended part of the program. I didn't check macOS, or a descendant that stays in the root's process group (that path goes through settlement and may already report cleaned).

Originally created by @Rinkia on GitHub (Oct 29, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/8032 ### Before submitting - [x] I searched the open and closed issues for a related report (cleanup_outcome not_required, process capture cleanup, detached descendant, setsid, settlement, escaped process group). Related but different: #4005 (closed, SIGINT during capture cleanup), #6445 (closed, exit code for a signal-terminated target). - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area Process capture or runtime observation ### Observed behavior A captured command forks a child that calls `setsid()` and keeps working after the parent exits. REA's owned-process cleanup finds the child through its run token and kills it with SIGKILL. The terminal progress line for that run says so: ``` ... disposition=exited owned_process_group=cleaned terminal_renderer=cleaned temporary_root=cleaned ``` The capture Evidence doesn't reflect that: - `settlement` is `{"state": "quiesced", "cleanup_outcome": "not_required"}`. - `cleanup.owned_process_group` is `"verified"`. - `process_samples` is empty, and `residual_unknowns` only has the generic "Process trees are sampled and may omit short-lived descendants." So the Evidence says no cleanup was needed, while REA ended a live process that the target started. The target's observable behavior also changes: run directly, the child finishes and writes its log. Under capture, the child is killed within about half a second, so `filesystem_effects` shows a log that was cut short. Nothing in the Evidence explains that. In `ProcessHarness.ts` and `ProcessCaptureLifecycle.ts`, `cleanup_outcome` is derived only from `capture.settlement.state` (`quiesced` gives `not_required`). The `signaled` flag that the ownership cleanup returns isn't used. `compare-process-captures` doesn't overclaim here. It reports `process: unknown` against a run of `/bin/echo parent-exit`. ### Reproduction I used a self-written probe in a Linux x64 container (`--network none`) on Ubuntu 24.04. I ran it on `rea-agents@6.8.0` (CLI and MCP) and on `main` at `45cc93596` (MCP). The results were the same. ```c /* orphan.c: the parent exits at once; a setsid child logs ticks for 5 s and logs any catchable signal */ #include <signal.h> #include <stdio.h> #include <unistd.h> static FILE *log_file; static void on_sig(int s) { fprintf(log_file, "signal %d\n", s); fflush(log_file); _exit(0); } int main(void) { if (fork() == 0) { setsid(); log_file = fopen("orphan.log", "w"); for (int s = 1; s < 32; s++) if (s != SIGKILL && s != SIGSTOP) signal(s, on_sig); fprintf(log_file, "child pid=%d ppid=%d\n", getpid(), getppid()); fflush(log_file); for (int i = 0; i < 10; i++) { usleep(500000); fprintf(log_file, "tick %d\n", i); fflush(log_file); } fputs("finished\n", log_file); fclose(log_file); return 0; } printf("parent-exit\n"); return 0; } ``` ```json {"executable": "/p/orphan", "working_directory": "/tmp/pc", "filesystem_observation_paths": ["/tmp/pc"], "timeout_ms": 5000} ``` `rea capture-process scenario.json --json`, or the same object through MCP `capture_process_scenario`. - Direct run: `orphan.log` gets 10 ticks and `finished`. - Under capture: `orphan.log` stays at the single `child pid=... ppid=1` line, even after waiting 6 seconds. No catchable signal is logged, which points to SIGKILL. The settlement is the one shown above. ### Expected behavior When cleanup signals a process, the Evidence should say so. For example, `cleanup_outcome: "cleaned"`, plus a residual unknown or limitation saying that a token-owned descendant was still running after the root exited and was terminated, so later filesystem and output observations may differ from an uncaptured run. Killing the descendant may well be the intended design. The issue is only that the Evidence reports it as not needed. ### Impact and scope This affects Linux process capture of targets that start detached workers, daemons or background writers. Someone reading the Evidence sees a clean, quiesced run and can't tell that REA ended part of the program. I didn't check macOS, or a descendant that stays in the root's process group (that path goes through settlement and may already report `cleaned`).
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#4661
No description provided.