CB-581: fail-safe release() so a throw never orphans the pane #59

Closed
agent wants to merge 0 commits from worker/cb581-d24826-5 into main
Member

Fixes gitea issue #57.

release() called worktrees.hasUncommitted() (shells out to git status) with no protection. Any throw there skipped notifyReleased and launcher.stop, leaving an orphaned pane still burning a fleet slot and any blocked bridge_send caller unresolved. reapIdle() called release() bare inside a loop, so one throwing session aborted the rest of the reaping pass.

Changes (SessionManager.java):

  • release(): wraps the dirty-check/memberLifecycle block in try/catch/finally. On any throw, the worktree is preserved (fail toward not destroying uncommitted work, per the CB-576 precedent) and a WARN is logged naming the pane, terminal, and worktree. notifyReleased runs in finally; launcher.stop(paneId) runs unconditionally afterward so the pane always stops.
  • reapIdle(): wraps each per-session release() in try/catch (matching drainAll's existing shape), logging a WARN naming pane/terminal/worktree, so one bad session can't skip the rest of the pass.

Tests (SessionManagerTest.java, package-local RecordingWorktrees double since FakeWorktrees/WorktreeSessionManagerTest were out of scope for this ticket):

  • releasePreservesWorktreeWhenDirtyCheckThrows
  • releaseStillStopsThePaneWhenDirtyCheckThrows
  • releaseStillNotifiesTheListenerWhenDirtyCheckThrows
  • reapIdleSurvivesOneSessionThatFailsToRelease (3 idle sessions, middle one's worktree removal throws — first and third still released, all three panes stopped)
  • unchangedRegressionCleanCompletedReleaseStillRemovesTheWorktree / ...DirtyCompletedReleaseStillPreservesTheWorktree / ...ShutdownDrainStillPreservesTheWorktree

mvn clean install: BUILD SUCCESS, Tests run: 717, Failures: 0, Errors: 0, Skipped: 0 (SessionManagerTest alone: 32 tests, 0 failures).

Fixes gitea issue #57. release() called worktrees.hasUncommitted() (shells out to git status) with no protection. Any throw there skipped notifyReleased and launcher.stop, leaving an orphaned pane still burning a fleet slot and any blocked bridge_send caller unresolved. reapIdle() called release() bare inside a loop, so one throwing session aborted the rest of the reaping pass. Changes (SessionManager.java): - release(): wraps the dirty-check/memberLifecycle block in try/catch/finally. On any throw, the worktree is preserved (fail toward not destroying uncommitted work, per the CB-576 precedent) and a WARN is logged naming the pane, terminal, and worktree. notifyReleased runs in finally; launcher.stop(paneId) runs unconditionally afterward so the pane always stops. - reapIdle(): wraps each per-session release() in try/catch (matching drainAll's existing shape), logging a WARN naming pane/terminal/worktree, so one bad session can't skip the rest of the pass. Tests (SessionManagerTest.java, package-local RecordingWorktrees double since FakeWorktrees/WorktreeSessionManagerTest were out of scope for this ticket): - releasePreservesWorktreeWhenDirtyCheckThrows - releaseStillStopsThePaneWhenDirtyCheckThrows - releaseStillNotifiesTheListenerWhenDirtyCheckThrows - reapIdleSurvivesOneSessionThatFailsToRelease (3 idle sessions, middle one's worktree removal throws — first and third still released, all three panes stopped) - unchangedRegressionCleanCompletedReleaseStillRemovesTheWorktree / ...DirtyCompletedReleaseStillPreservesTheWorktree / ...ShutdownDrainStillPreservesTheWorktree mvn clean install: BUILD SUCCESS, Tests run: 717, Failures: 0, Errors: 0, Skipped: 0 (SessionManagerTest alone: 32 tests, 0 failures).
agent added 1 commit 2026-08-15 10:09:52 +02:00
CB-581: fail-safe release() so a throw never orphans the pane or aborts reapIdle
CI / build (pull_request) Successful in 52s
CI / contract (pull_request) Successful in 1m1s
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.
ltms closed this pull request 2026-08-15 12:47:36 +02:00
Some checks are pending
CI / build (pull_request) Successful in 52s
CI / contract (pull_request) Successful in 1m1s

Pull request closed

Sign in to join this conversation.