fix(orphan): prevent Claude Code session leaks on macOS

Three bugs were causing orphaned Claude processes to accumulate:

1. TTY comparison in orphan.go checked for "?" but macOS shows "??"
   - Orphan cleanup never found anything on macOS
   - Changed to check for both "?" and "??"

2. selfKillSession in done.go used basic tmux kill-session
   - Claude Code can survive SIGHUP
   - Now uses KillSessionWithProcesses for proper cleanup

3. Crew stop commands used basic KillSession
   - Same issue as #2
   - Updated runCrewRemove, runCrewStop, runCrewStopAll

Root cause of 383 accumulated sessions: every gt done and crew stop
left orphans, and the cleanup never worked on macOS.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
mayor
2026-01-17 03:49:18 -08:00
committed by beads/crew/emma
parent 4a856f6e0d
commit 2feefd1731
3 changed files with 14 additions and 13 deletions

View File

@@ -3,7 +3,6 @@ package cmd
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
@@ -15,6 +14,7 @@ import (
"github.com/steveyegge/gastown/internal/polecat"
"github.com/steveyegge/gastown/internal/rig"
"github.com/steveyegge/gastown/internal/style"
"github.com/steveyegge/gastown/internal/tmux"
"github.com/steveyegge/gastown/internal/townlog"
"github.com/steveyegge/gastown/internal/workspace"
)
@@ -743,11 +743,11 @@ func selfKillSession(townRoot string, roleInfo RoleInfo) error {
_ = events.LogFeed(events.TypeSessionDeath, agentID,
events.SessionDeathPayload(sessionName, agentID, "self-clean: done means gone", "gt done"))
// Kill our own tmux session
// This will terminate Claude and the shell, completing the self-cleaning cycle.
// We use exec.Command instead of the tmux package to avoid import cycles.
cmd := exec.Command("tmux", "kill-session", "-t", sessionName) //nolint:gosec // G204: sessionName is derived from env vars, not user input
if err := cmd.Run(); err != nil {
// Kill our own tmux session with proper process cleanup
// This will terminate Claude and all child processes, completing the self-cleaning cycle.
// We use KillSessionWithProcesses to ensure no orphaned processes are left behind.
t := tmux.NewTmux()
if err := t.KillSessionWithProcesses(sessionName); err != nil {
return fmt.Errorf("killing session %s: %w", sessionName, err)
}