8fd2d7e5e7
hasUncommitted shells out to git and can throw; release() now catches that inside a try/finally so notifyReleased and launcher.stop always run, and defaults to preserving the worktree on a throw (can't tell dirty vs clean, so don't risk deleting unrecoverable work). reapIdle wraps each per-session release in try/catch, matching drainAll, so one bad session no longer skips the rest of the reaping pass.