# fix\(server\): reap recorded Linux Claude survivors before recovery · PR \#22833 · pingdotgg/t3code

[View on GitCafe](https://git.cafe/pingdotgg/t3code/pull/22833)

Repository: [pingdotgg/t3code](https://git.cafe/pingdotgg/t3code)

Visibility: public

State: open

Source repository: pingdotgg/t3code

Source branch: fix/issue-15357

Target branch: main

Head commit: bc7446704898fbdd19e25542f0924e0472e9f655

[Discussion and reviews](https://git.cafe/pingdotgg/t3code/pull/22833)

[Commits](https://git.cafe/pingdotgg/t3code/pull/22833/commits?format=markdown)

[Files changed](https://git.cafe/pingdotgg/t3code/pull/22833/diffs?format=markdown)

## Description

---

## Problem

Addresses [#15357](https://git.cafe/pingdotgg/t3code/issues/15357).

## Change

On main at [1688f96568](https://git.cafe/pingdotgg/t3code/commit/1688f96568), killing the backend during two foreground Claude `sleep 120` turns leaves the CLIs alive after restart, while recovery marks their runs cancelled and starts continuation. The existing systemd slice cleanup handles hosts with a user manager; a Linux server without one still leaks the original agents.

## Scope and approval

Fixes #15357

Triaged bug via the automated issue flow.

Validation: 226 scoped tests passed; server typecheck and formatting passed. Scoped lint passed with one pre-existing unused-variable warning. The three initial regression tests failed against main before implementation. Independent Codex GPT-6.1-Sol and Claude Opus 5.5 code reviews both returned MERGE with no confirmed findings. Codex independently ran 229 focused tests across five files. Both proof audits returned SUFFICIENT for the documented Linux scope.

Not implemented or verified: macOS and Windows process groups. Unverified: desktop-bundled Linux server. [Systemd-enabled runtime transcript](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/734ccf5c6ce91a0c/systemd-runtime.txt) confirms both Claude CLIs and all recorded scope members are gone after restart, with systemd cleanup logged before recovery. In that comparison Codex had an active sleep before SIGKILL; its CLI and tool processes exited before restart. The first baseline did not capture Codex foreground tool startup.

Runtime covers **3/7 matrix rows**; **6/7 have accepted runtime/scoped evidence**, with R7 an explicit exclusion. Pass-1 proof audit returned SUFFICIENT and independently passed **213 tests in 2 files**. The code reviewer independently passed **229 tests in 5 files**; the initial implementation passed **226 scoped tests**. These are separate runs. Both round-1 code reviewers returned MERGE with no confirmed findings. [Pass-2 proof audit](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/7c72746495bbaf50/proof-pass2.md) returned SUFFICIENT and independently rebuilt the ownership fixture: 1 passed, 9 skipped by name filter. Packaging did not rerun tests or recapture app sessions.

Existing committed fixtures fulfill scoped rows; no additional seeding data is needed. App setup follows the UI procedure in the index. The evidence packaging added no implementation changes or committed logs/assets/plans.

## Verification

Adds a Linux Claude process ledger, an execution gate that records the child before the CLI runs, and startup reconciliation before recovery. Cleanup checks the boot ID and process start identity, preserves live owners and unmatched PIDs, and signals only recorded survivors. Systemd cleanup now runs at startup too.

Evidence: [baseline reproduction](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/7dc988f8c7ec9126/baseline.md), [PID transcript](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/c0b087d22e41cd37/runtime.txt).

After: [runtime transcript at bc74467048](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/7395aef4b9aa4d6d/after-runtime.txt). Recorded Claude PIDs 53331 and 53871 were gone after restart; original runs were cancelled and continuation runs started. Bash/sleep descendants were present at the post-restart sample; this change stops the recorded CLIs, not arbitrary grandchildren. Node retains an identity-read-to-signal race without pidfds. Pre-upgrade survivors without ledger records are not cleaned up.

Model and harness: GPT-6.1-Sol through Codex in T3 Code.

### Evidence

[Standalone evidence index](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/088d4eb81794ac4d/evidence-index.md) · [Accepted seven-row matrix / pass-1 proof audit](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/f172d571e3c50874/proof-matrix.md)

Before: [1688f96568](https://git.cafe/pingdotgg/t3code/commit/1688f965682d331da16a8c39f049592361f69c8d), full SHA `1688f965682d331da16a8c39f049592361f69c8d` (pinned merge-base). After: [bc74467048](https://git.cafe/pingdotgg/t3code/commit/bc7446704898fbdd19e25542f0924e0472e9f655), full SHA `bc7446704898fbdd19e25542f0924e0472e9f655`.

Existing primary captures: Linux x86_64 source dev server, real provider CLIs, real browser on `localhost:6475`, backend `localhost:14515`. Explicit isolated homes with `T3CODE_HOME` unset; no-systemd branch also unsets `XDG_RUNTIME_DIR`. Full command/UI procedure and exact homes are in the index. No UI rendering changed; screenshots, DPR, theme and viewport comparisons do not apply under the server-only override.

| Row | Before | After / accepted proof | Limit |
| --- | --- | --- | --- |
| R1 no-systemd Claude | [Baseline](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/c0b087d22e41cd37/runtime.txt): backend 20934; Claude 22811/23324 survive restart. | [After](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/7395aef4b9aa4d6d/after-runtime.txt): backend 51528; Claude 53331/53871 absent after restart. | Pre-recovery ordering is proved by scoped production-layer subprocess test plus server wiring, not by runtime sample timing. |
| R2 systemd Claude | [Baseline](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/c0b087d22e41cd37/runtime.txt) / [narrative](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/7dc988f8c7ec9126/baseline.md): recovery precedes lazy cleanup. | [After](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/734ccf5c6ce91a0c/systemd-runtime.txt): cleanup call 11:46:22.587 precedes recovery 11:46:23.986; old CLI/tool PIDs absent after restart. | Later absence plus awaited stop and scoped acquisition test; no exact per-PID exit time. |
| R3 Codex comparison | [Baseline](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/c0b087d22e41cd37/runtime.txt): wrapper/CLI only, no captured active tool. | [After](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/734ccf5c6ce91a0c/systemd-runtime.txt): active CLI/tool present before crash, absent before restart; continuation completes. | Meaningful active-tool evidence is systemd at H only. No Codex adapter change or new ledgering. |
| R4 identity preservation | No dedicated before runtime; [matrix seeds](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/f172d571e3c50874/proof-matrix.md). | [Committed scoped fixtures at H](https://git.cafe/pingdotgg/t3code/blob/bc7446704898fbdd19e25542f0924e0472e9f655/apps/server/src/process/agentScope.test.ts). | Real Node subprocess fixtures; PID reuse simulated by identity mismatch, not forced OS recycling. |
| R5 launch gate | No dedicated before boundary capture; [matrix seeds](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/f172d571e3c50874/proof-matrix.md). | [Committed SDK/default-spawn fixture at H](https://git.cafe/pingdotgg/t3code/blob/bc7446704898fbdd19e25542f0924e0472e9f655/apps/server/src/process/agentScope.test.ts): fake CLI observes its record at entry and removal on close. | Production SDK with credential-free fake CLI, not a real-provider boundary trace. |
| R6 completion/cancel | No real-Claude lifecycle capture; [matrix R6](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/f172d571e3c50874/proof-matrix.md). | [Adapter fixtures](https://git.cafe/pingdotgg/t3code/blob/bc7446704898fbdd19e25542f0924e0472e9f655/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts) plus SDK fake-CLI close fixture. | Scoped compatibility accepted; actual Claude normal exit/cancel remains unverified. |
| R7 platforms/artifacts | No capture; [matrix R7](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/f172d571e3c50874/proof-matrix.md). | No runtime verification. | macOS, Windows, desktop-bundled Linux and every-provider launch compatibility unverified. |

What else changed: Linux Claude CLI launch gate/identity ledger and startup cleanup order. Visible cancellation and continuation behavior remains unchanged in the crash captures. Bash/sleep descendants remain at the no-systemd post-restart sample; their later exit was not observed. Arbitrary-grandchild cleanup and pre-upgrade unrecorded survivors are excluded; the identity-read-to-signal race remains without pidfds. Codex has no adapter change. No UI changes.

Raw dev logs contain pairing credentials and were not uploaded. Only existing selected/redacted public transcripts are linked. No new servers/browsers were launched and no new server cleanup was needed.

Evidence packaged by GPT-6.1-Sol through Codex in T3 Code; runtime captures were supplied by the primary agent.

