Skip to content

fix(app): kill processes inside a worktree before removing it#1422

Open
chphch wants to merge 1 commit into
slopus:mainfrom
chphch:fix/worktree-remove-kills-orphan-sessions
Open

fix(app): kill processes inside a worktree before removing it#1422
chphch wants to merge 1 commit into
slopus:mainfrom
chphch:fix/worktree-remove-kills-orphan-sessions

Conversation

@chphch

@chphch chphch commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Removing a git worktree from the app (removeWorktree in sources/utils/worktree.ts) ran git worktree remove --force while a happy session could still be cwd'd inside that worktree — most often a session that got orphaned when its daemon restarted, so the daemon-driven stop never reached it. --force then deleted the directory out from under the live process, which kept its server socket open and re-reported the session as active on every heartbeat. The session became impossible to archive from the app: each active: false was instantly overwritten by the orphan's next heartbeat. This change stops any process whose working directory is inside the worktree (SIGTERM, best-effort) before the directory is removed.

The new killProcessesInWorktree is portable across the host OS (Linux reads /proc/<pid>/cwd, macOS falls back to lsof), canonicalises the worktree path (pwd -P) so a symlinked parent still matches, and anchors the match to the worktree dir or a subdir of it so a sibling that merely shares a name prefix is never touched. It resolves lsof by absolute path so a stripped PATH still finds it, and never blocks the removal if anything goes wrong.

Proof

Ran the exact killProcessesInWorktree shell — the macOS/lsof branch — verbatim against a real process tree, handing it a symlinked worktree path to exercise canonicalisation:

worktree-orphan-kill

  • process inside the worktree → killed
  • process in a subdirectory of the worktree → killed
  • process in a prefix-sibling dir (wt-sibling) → spared (anchoring — a shared name prefix is not a match)
  • the symlinked path …/link/wt was canonicalised to …/real/wt, so lsof's physical paths matched
  • lsof was not on PATH, so the /usr/sbin/lsof fallback was exercised

removeWorktree calls this immediately before git worktree remove --force (see the diff), so the orphaned session is shut down cleanly and can be archived from the app as expected.

`removeWorktree` ran `git worktree remove --force` while a happy session
could still be cwd'd inside the worktree — typically a session that was
orphaned when its daemon restarted, so R2/daemon-driven stop never reached
it. `--force` then deleted the directory out from under the live process,
which kept its server socket open and re-reported the session as `active`
indefinitely. The session could never be archived from the app: every
`active:false` was immediately overwritten by the orphan's next heartbeat.

Stop those processes before deleting the directory. `killProcessesInWorktree`
matches by working directory (Linux: /proc/<pid>/cwd, macOS: lsof), anchored
to the worktree dir or a subdir of it so siblings sharing a name prefix are
spared, and canonicalises the path so a symlinked parent still matches.
SIGTERM lets the happy CLI shut its session down cleanly. Best-effort — a
failure here never blocks the removal.
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.

1 participant