fix(cli): bind isolation ownership to process start-time token

A crashed owner's pid can be recycled by an unrelated long-lived
process, so kill(pid, 0) succeeds and the leftover sandbox was pinned
live forever, unreachable by a non-`--all` clear.

The ownership marker now records a process-instance start-time token
alongside the pid (Linux /proc/<pid>/stat field 22, other Unix via
`ps -o lstart`). A live pid whose current token no longer matches the
recorded one is a recycled pid and counts as dead; platforms that can't
report a token degrade to the prior pid-only check.

Fixes #6761
This commit is contained in:
roboomp
2026-07-27 03:52:30 +00:00
parent 91c0feaa87
commit 01429d83e2
3 changed files with 62 additions and 5 deletions
@@ -63,6 +63,14 @@ describe("worktree clear task-isolation ownership", () => {
await fs.mkdir(pending, { recursive: true });
await writeIsolationOwner(pending, "pend0005");
// Recycled pid: the crashed owner's pid was reassigned to this live test
// process, but the recorded start-time token no longer matches.
const recycled = await makeSandbox("trecyc06");
await Bun.write(
path.join(recycled, ISOLATION_OWNER_FILE),
JSON.stringify({ pid: process.pid, id: "recyc06", startToken: "not-the-current-token" }),
);
await clearWorktrees({ all: false, dryRun: false, json: true });
const exists = async (p: string): Promise<boolean> =>
@@ -75,5 +83,6 @@ describe("worktree clear task-isolation ownership", () => {
expect(await exists(orphan)).toBe(false);
expect(await exists(corrupt)).toBe(false);
expect(await exists(pending)).toBe(true);
expect(await exists(recycled)).toBe(false);
});
});