Skip to content

feat: reap orphaned agent processes on launch#102

Open
gi11es wants to merge 2 commits into
masterfrom
feat/reap-orphaned-agents
Open

feat: reap orphaned agent processes on launch#102
gi11es wants to merge 2 commits into
masterfrom
feat/reap-orphaned-agents

Conversation

@gi11es

@gi11es gi11es commented Jul 13, 2026

Copy link
Copy Markdown
Owner

Fixes #100.

When Deckard dies abnormally, spawned claude/codex processes survive as orphans (they ignore the PTY hangup), and session restore then resumes the same sessions again — two live processes appending to one session jsonl. Observed at 41-orphan scale after a single force-kill.

Approach

  • New SpawnedProcessRegistry: persists direct-spawn (non-tmux) shell PIDs to spawned-pids.json with the kernel process start time (via sysctl) to guard against PID reuse.
  • TerminalSurface records on spawn and removes on terminate()/detach()/processTerminated.
  • AppDelegate calls reapOrphans() at startup before the window controller spawns restores: recorded PIDs that are alive, start-time-matched, and reparented to launchd (ppid == 1) are killed. Entries still parented (e.g. a second live Deckard instance) are kept untouched. tmux sessions remain persistent by design.
  • Decision logic is a pure function with unit tests (dead pid, reused pid, parented, orphan), plus registry persistence round-trip tests. Full suite passes.

🤖 Generated with Claude Code

gi11es and others added 2 commits July 14, 2026 00:35
When Deckard dies abnormally, spawned claude/codex processes survive as
orphans (they ignore the PTY hangup), and session restore then resumes
the same sessions again — two live processes appending to one session
file. Observed at 41-orphan scale after a single force-kill.

Track direct (non-tmux) shell PIDs in a persistent registry keyed by
kernel process start time (guards against PID reuse). On launch, before
restore spawns replacements, kill recorded processes that are alive but
reparented to launchd. Entries owned by another live instance are left
alone; tmux sessions remain persistent by design.

Fixes #100

Co-Authored-By: Claude Fable 5 <[email protected]>
- terminate()/detach() no longer remove registry entries: they only
  express kill intent, and agents observably survive SIGTERM — eager
  removal made such survivors permanently unreapable. Entries are
  removed on confirmed exit (processTerminated) or reaped next launch.
- per-instance registry files (spawned-pids-<pid>.json) stamped with
  the owner's pid + start time replace the shared last-writer-wins
  file, so concurrent instances (Debug build + installed app) cannot
  lose each other's records; reap skips files whose owner is alive and
  processes/deletes files of dead instances (legacy ownerless format
  still consumed; undecodable files left untouched)
- decide() hard-guards pid > 0 (kill(0)/kill(-1) target process groups)
- kill() failures are logged and the entry retained for a later attempt
- versioned file format; kept entries are written back, verified by test

Co-Authored-By: Claude Fable 5 <[email protected]>
@gi11es

gi11es commented Jul 13, 2026

Copy link
Copy Markdown
Owner Author

Subagent review follow-up (pushed): (1) terminate()/detach() no longer remove registry entries — SIGTERM survivors stayed reapable only if the entry outlives kill intent; removal now happens solely on confirmed exit. (2) Replaced the shared registry file (last-writer-wins across concurrent instances) with per-instance spawned-pids-<pid>.json stamped with owner pid + start time; reap skips live owners' files and processes/deletes dead ones (legacy format still consumed, undecodable files left alone). (3) decide() hard-guards pid > 0. (4) kill() failures logged and retained for retry. (5) versioned format; kept-entry write-back now actually asserted in tests (15 tests).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Abnormal app exit leaves orphaned claude fleet; restore double-resumes their sessions

1 participant