Register broker ownership and safely reclaim abandoned helper trees - #563
Register broker ownership and safely reclaim abandoned helper trees#563ccheng555 wants to merge 30 commits into
Conversation
Attributed-To: Codex interactive
Attributed-To: Codex interactive
Attributed-To: Codex interactive
Attributed-To: Codex interactive
Attributed-To: Codex interactive
… non-fatal cleanup verification, async non-blocking process cleanup, ps portability with degraded fallback, de-flaked idle-release tests Attributed-To: Codex executor lane (implementation), Claude interactive (verification, test-anchor fix) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MrRkob3GtGnbYqq6aeE5co
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 38529f436e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…s, keep stream ownership on socket close, reap descendants before their parent Attributed-To: Claude interactive Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MrRkob3GtGnbYqq6aeE5co
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 16547a0cc8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…kill phase, reclaim orphaned process group on unexpected child exit Attributed-To: Claude interactive Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MrRkob3GtGnbYqq6aeE5co
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 58d84b1801
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
A TERM-resistant helper whose parent already exited is reparented out of the root's tree, so it was signaled by the group kill but never tracked: polling saw no live records, skipped SIGKILL, and reported verified while the helper was still alive. Group members observed at scan time now enter the shared tracked map, so they escalate and count as survivors. Attributed-To: Codex executor lane Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
connect() rejected before returning the client, so a detached app-server that answered initialize with an RPC error but stayed alive was never closed, and every later request could spawn another live helper tree. connect() now closes its own client on a failed initialize and relays an unverified cleanup outcome to the broker's existing refusal gate. Attributed-To: Codex executor lane Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 386411c99f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
An adversarial audit found this branch's cleanup layer repeatedly claiming success it could not prove: an empty process-group selection, a missing root, and a dead root all produced verified: true while a detached helper could still be alive. This replaces those claims with one contract. Ownership is captured while the root is alive, so cleanup consults what it recorded owning rather than rediscovering a tree whose root has since exited. Absence of evidence is no longer proof of death: an unreadable process table, an unaccounted-for owned descendant, or a missing root all report verified: false. A root positively observed absent from a readable table, with nothing owned unaccounted for, is still a clean success, so cancelling an already-exited job does not surface a spurious error. Survivors are tracked as pid@lstart identities rather than bare pids, so pid reuse can neither mask a survivor nor wedge the broker permanently. Also: identity-capture failure no longer pre-kills the child in a way that made close() skip tree cleanup and suppress the unverified-cleanup gate; the broker closes its listener before awaiting child cleanup during shutdown, so a late client cannot start a replacement app-server it would then abandon; and callers no longer discard an unverified cleanup outcome and delete the ownership records needed to retry it. Attributed-To: Codex executor lane Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
@codex review |
|
Status note for anyone reading the review threads: the automated passes above all ran against commits that predate the current head ( Resolving the ones the current head addresses:
Left open deliberately: the two findings about reaping helpers before their app-server parent and keeping the root out of the descendant phase. Both are the same gap on systems where nothing reaps orphans, and both are named as out of scope in the PR description rather than silently dropped. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c663c57f41
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Correction to my previous note: I resolved the "Reclaim independently grouped helpers after a child crash" thread on faulty reasoning and have reopened it. I had read The two findings from the review of
Working on both. |
Cleanup could adopt the identity of whatever process currently held a recorded pid, which makes identity verification a no-op and can signal an unrelated process tree after pid reuse. Background task records persisted only a pid, so that was the live path for cancel and session-end cleanup. - Remove the current-identity fallback; a record with no ownership evidence refuses to signal and reports unverified rather than killing. - Capture ownership when spawning background workers and brokers so the normal path stays fully verified. - Allow best-effort cleanup after an identity-capture failure only when the caller still holds a live child handle, which is what makes pid reuse impossible; persisted records never qualify and are left alone with a message saying so. Leaking a process is preferred over killing a stranger. - Include a live member of an owned process group even when it postdates the ownership snapshot, so a helper spawned after initialize is reclaimed instead of wedging the broker against ever spawning a replacement. Attributed-To: Codex executor lane (implementation), Claude interactive (review, verification) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Pushed Refuse to adopt an unverified current PID. The fallback that took the current holder of a recorded pid as the expected identity is gone. A record with no ownership evidence now refuses to signal and reports unverified, rather than killing whatever inherited that pid. Background workers and broker sessions capture ownership at spawn time so the normal path stays fully verified instead of merely permitted. The one case that still needs best-effort cleanup is an identity-capture failure. That is now allowed only when the caller still holds a live, unreaped child handle, since that is the specific condition that makes pid reuse impossible. Persisted records never qualify: they are left alone with a message saying the job could not be verified as owned. Leaking a process is the intended outcome there. Worth stating plainly, because I got it wrong first: an earlier version of this fix persisted the capture-failure flag into the job record and honoured it on cancel and session end. That reintroduced the same defect by a different route, since a record on disk outlives its process. Caught on review, not by the tests, and there is now a test that fails specifically on it. Reclaim helpers created after the ownership snapshot. A live member of the owned process group is included even when it postdates the snapshot, so a helper spawned after Verification. Full suite: 118 tests, 114 pass, 4 fail — the same 4 environmental failures present on the base commit, by name. Four new tests. Reverting the source while keeping the tests fails exactly those four; reverting only the live-handle gating fails exactly the one test written for it. Still deliberately out of scope, as described in the PR body: reaping helpers before their app-server parent, and keeping the root out of the descendant phase, both on systems where nothing reaps orphans. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ce3a15e9ae
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Acknowledging the startup-race finding: it is real and it is mine. Ownership capture is a synchronous process-table scan sitting between the worker spawn and the job record being written, so a worker that starts quickly can read a record that does not exist yet, exit with The obvious fix — persist before spawning, then patch ownership onto the stored record — is not sufficient, so I am not pushing it. It moves the race rather than removing it: the worker loads the job record at startup and writes back a record derived from that load, so the parent's ownership patch and the worker's first write contend, and whichever lands second wins. Measured on a full unsandboxed run, that reordering takes the suite from 118 tests / 114 pass / 4 fail to 120 / 115 / 5, with The shape that should work is keeping ownership out of the record the worker rewrites — a sidecar the parent owns and the worker never touches — so there is no contention to order. I would rather land that than a reordering that trades one race for another. Head stays at |
Ownership data had no single author. The parent wrote a job record at enqueue time and the worker rewrote the same file as it ran, both through unlocked whole-record writes, so correctness depended on an ordering that neither side controlled. Successive fixes kept trading one race for another. The invariant now enforced by data ownership rather than by conditionals: a process identity may be recorded only by the process it describes, or by a holder of a live unreaped handle to it, and every persisted file has exactly one writer at a time. - The enqueuer writes the queued record before spawning and never writes it again. It no longer scans the process table, which removes the window where a fast worker read a record that did not exist yet and exited. - The worker records its own identity. A process scanning for itself cannot be confused by pid reuse, because it is alive while scanning. - Task records no longer carry an ownership snapshot. Descendants and process-group members are still discovered at termination time, and the worker leads its own group, so late-spawned helpers are still reclaimed. - Cancellation of a job whose worker has not started yet uses a create-only flag file whose existence is the whole message, so it cannot be contended. This closes a case where cancelling a queued job reported success while the worker went on to run it to completion. - Spawn sites that hold a live child handle keep capturing ownership directly; that is the other half of the invariant, and it is unchanged. Attributed-To: Codex executor lane (implementation), Claude interactive (design review, verification) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Pushed The startup race. Ownership capture is a process-table scan, and it sat between spawning the background worker and writing its job record. The enqueuer no longer captures ownership at all; it writes the record before spawning and never writes it again. The worker records its own identity instead. A process scanning for itself cannot be confused by pid reuse, because it is alive while scanning — which is the property every earlier version of this code was missing. I tried the obvious fix first (persist, spawn, then patch ownership onto the record) and did not push it, because it moves the race rather than removing it: the worker loads that record at startup and writes back a record derived from it, so the parent's patch and the worker's first write contend. Measured, that reordering took the suite from 4 failures to 5, with A case that was not reported and is worth flagging. On The change shrinks the surface. Ownership capture drops from three call sites to two, both of which hold a live unreaped child handle. Task records no longer carry an ownership snapshot; descendants and process-group members are still discovered at termination time, and the worker leads its own group, so late-spawned helpers are still reclaimed — there is a test for that specifically. The invariant this all rests on is now stated in the PR description, so future changes have something to be checked against rather than being re-derived per call site. Verification. 124 tests, 120 pass, 4 fail — the same 4 pre-existing environmental failures, by name. Reverting only the cancel-flag check fails exactly the two flag-protocol tests and nothing else, which isolates them to the behaviour rather than to the feature's general absence. One flaky test is disclosed in the description rather than left to be discovered. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5f27c1a2f0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Pushed This finding was real and worth fixing. Retaining stream lifetime after a client disconnects left one piece still keyed on the socket, so a client that dropped before a detached Stated plainly: unlike every other fix here, this one has no dedicated regression test. Covering it needs a broker integration test around detached review streams, which is the least stable part of this suite, and I would rather ship a small reasoned fix with the gap named than either skip it or destabilise the suite chasing it. Full suite passes unchanged: 128 tests, 124 pass, 4 pre-existing environmental failures. Closing position. Every finding in this review series has been real, and several were introduced by earlier fixes in the series — that history is in the description rather than tidied away. The branch is now at the state I intend to defend: the invariant governing process ownership is written into the description, the known limits are listed there including the one-second identity resolution window and the zombie-ordering gaps, and each remaining open thread is deliberate rather than overlooked. Further automated findings will not be triaged into this branch unless they identify a violation of the stated invariant. Anything that requires widening the guarantee belongs in follow-up work against #543 and #526, where the surrounding design questions can be settled by someone who owns this area. Continuing to expand scope in an unreviewed branch trades a defensible change for a larger one nobody has read. Happy to rebase, split, or revise this if a maintainer picks it up. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 19ac644d82
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Attributed-To: Codex
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6e8659be6c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Attributed-To: Codex executor lane
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9d0b378d42
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Attributed-To: Codex
|
@codex review Please review exact head
Please treat any confirmed violation of the registered-only ownership invariant as actionable. Suggestions that widen cleanup to unregistered or heuristic targets are intentionally out of scope. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8cec19f8fc
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Attributed-To: Codex
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8fbd95872f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Attributed-To: Codex
Attributed-To: Codex
|
@codex review Please review exact head 58c1ea3 as one bounded final architecture pass. Focus only on violations of these invariants:
Please flag only confirmed executable violations on this head. Do not suggest heuristic cleanup of unregistered processes. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 58c1ea3158
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Attributed-To: Codex
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 328bdd56f9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Attributed-To: Codex
|
@codex review Please review exact head 4e8884b as one bounded follow-up pass. Focus only on confirmed executable violations of these invariants:
Please flag only a concrete path that violates those invariants on this head. Suggestions for heuristic cleanup of unregistered processes remain out of scope. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4e8884be6e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Attributed-To: Codex
Attributed-To: Codex
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4f6a87f181
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Attributed-To: Codex
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d17945529a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Attributed-To: Codex
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d735a66a62
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (message.method === "turn/completed" && activeStreamRunning) { | ||
| const threadId = message.params?.threadId ?? null; | ||
| if (!threadId || !activeStreamThreadIds || activeStreamThreadIds.has(threadId)) { |
There was a problem hiding this comment.
Defer completion matching until result IDs are known
When a detached review/start emits turn/completed before its response, the completion carries the returned review-thread ID while activeStreamThreadIds still contains only the source thread from the request. This condition rejects that completion, and the later result-derived assignment never replays it, so the client can finish while the broker remains permanently busy. Fresh evidence beyond the earlier disconnect finding is the supported pre-response-notification path in captureTurn, which explicitly buffers notifications until the start response arrives.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Valid. This is a broker liveness bug, not a registered-ownership escape: SessionEnd and registered crash recovery still reclaim the exact broker and child tree. It arrived after the agreed final safety pass, so I am leaving it unresolved for maintainer follow-up instead of starting another automated-review loop. Current head 00015e3 only adds the test-fixture lifetime guard and does not claim to fix this thread.
| // Streaming requests return before their turn is complete. Publish a | ||
| // fresh helper observation before forwarding each later notification | ||
| // so independently grouped helpers are durable before a crash can | ||
| // strand them outside both local and registered cleanup. | ||
| await this.refreshOwnership(); |
There was a problem hiding this comment.
Coalesce ownership refreshes for high-volume notifications
On Unix shared-broker turns, every app-server notification now invokes captureProcessOwnership, which synchronously spawns /bin/ps, and the promise chain serializes these scans before forwarding each notification. Turns with high-volume item or command-output updates therefore accumulate one full process-table scan and registry publication attempt per event, blocking the broker event loop and potentially delaying completion by seconds or minutes; debounce/coalesce observations while retaining a mandatory refresh at lifecycle boundaries.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Valid. Per-notification process-table capture should be coalesced while preserving mandatory lifecycle-boundary publication. This is a performance concern, not an unregistered-process or broadened-signal path. It arrived after the agreed final safety pass, so I am leaving it unresolved for maintainer follow-up instead of starting another automated-review loop. Current head 00015e3 only adds the test-fixture lifetime guard and does not claim to fix this thread.
Attributed-To: Codex
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 00015e3ba2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const snapshotRecord = recordsFromOwnershipSnapshot(ownershipSnapshot).find((candidate) => candidate.pid === record.pid); | ||
| if (ownershipSnapshot && snapshotRecord && snapshotRecord.identity !== record.identity) { | ||
| continue; | ||
| } | ||
| ownershipSelectionFound = true; |
There was a problem hiding this comment.
Block verification when any owned-scope member is excluded
When a PID from an older snapshot is reused by a new helper in the same owned process group or session, this branch skips that current member because the first snapshot record for its PID has a different identity. However, ownershipSelectionFound is global, so another matching member can set it to true; cleanup then signals only the tracked members and reports verified: true while the skipped helper remains alive. The broker/reaper can consequently release the child and permit a replacement despite the survivor, so verification must account for every current member of the owned scope rather than any one member.
Useful? React with 👍 / 👎.
| const processIdentity = (dependencies.getProcessIdentityImpl ?? getProcessIdentity)(process.pid); | ||
| workerOwnership = processIdentity ? { processIdentity } : { ownershipCaptureFailed: true }; |
There was a problem hiding this comment.
Persist direct children for worker-crash recovery
When a background worker falls back to a direct app-server and is killed abruptly after that detached app-server or a helper starts, this persists only the worker's identity. Cancel and SessionEnd later invoke terminateProcessTree with no ownership snapshot; once the worker PID is absent, cleanup reports verified without any way to rediscover its reparented direct child, allowing the app-server/helper to survive while the job record is removed. Persist worker-observed direct-child ownership or keep the direct child attached so an abnormal worker exit can be recovered.
Useful? React with 👍 / 👎.
| appClientRegistryChild = registryChild; | ||
| client.setNotificationHandler(routeNotification); | ||
| const childPid = client.proc?.pid ?? null; | ||
| client.setExitHandler(() => { |
There was a problem hiding this comment.
Replay exits registered after client initialization
If the spawned app-server exits after its initialization response while an ownership publication is awaiting lock contention, handleExit can run before this handler is installed. setExitHandler does not replay an already-resolved exit, so the broker caches the dead client and never clears or releases it; a subsequent request can add a pending RPC after exitPromise has already resolved and then hang or fail on the closed stdin. Install the exit handler before initialization can yield, or immediately invoke it when exitResolved is already true.
Useful? React with 👍 / 👎.
Adds durable ownership evidence and a registered lifecycle so Codex Companion broker, app-server, and helper trees do not depend on manual process-name cleanup. This expands the earlier app-server work into the ownership architecture tracked by CC-5219 and CC-5589, and relates to #543 and #526.
Root cause
A detached shared broker could outlive its session, and its app-server could be spawned or initialized before durable child ownership existed. SessionEnd could not run after a hard crash and was scoped to the session cwd, while pid files were not a durable identity handle. An external reaper therefore could not safely distinguish abandoned trees from live shared ownership. Process names, PPID 1, command text, age, and socket presence are observations, not authority to signal.
Ownership invariant
A process identity may be recorded only by that process or by a parent holding its live unreaped child handle. Automated cleanup is eligible only when the immutable broker registry is valid, at least one valid owner row exists, every unreleased owner is freshly proven absent or reused, and every target still matches the recorded identity immediately before signalling.
One live owner blocks cleanup. Missing, malformed, unreadable, overly permissive, symlinked, incomplete, locked, identity-mismatched, caller-supplied, manual, and unregistered state remains report-only.
What changed
codex app-server; broker death or publication failure before activation closes the control pipe and starts no Codex child.ps sessis an audit session and is explicitly never used as a containment boundary.Honest scope
This PR never infers ownership for manual broker starts or caller-supplied endpoints, and it does not add broad process-name cleanup.
No user-space harness can run cleanup at the instant the entire session is killed. The contract is immediate cleanup on normal SessionEnd and automatic registered-only convergence on the next SessionStart. If the harness never starts again, no future cleanup code can execute.
Unix process identity remains bounded by the platform surface. On systems where
ps lstarthas only whole-second resolution, this PR does not claim a kernel start-tick identity. Cleanup still requires immutable registered ownership plus fresh PID, start-time, process-group, and group-leader agreement, and fails closed on every observable mismatch, but a same-PID same-second replacement is an explicitly accepted portable boundary.Checks
npm test: 194 passed, 0 failed, 0 skipped.git diff --check, and the staged public-repo leak sweep are clean.Review closure
Current head:
4f6a87f1810b84a851b50dac85a5dc9c05674215.The prior exact-head Codex review found two remaining gaps: post-activation helper ownership and repeated cleanup of already-verified registries. Both are fixed with red-to-green regressions, and both review threads have implementation receipts and are resolved. The single bounded follow-up review found a P1 cleanup-authority containment gap and a P2 lock-contention gap;
4f6a87fandec40de8fix them with report-only structural validation and synchronous fail-closed teardown. Both threads are resolved.The bounded automated-review round is closed. No further automated review has been requested; new maintainer feedback can be handled as a new bounded change.