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:
@@ -60,11 +60,11 @@ func runCrewRemove(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Kill session if it exists
|
||||
// Kill session if it exists (with proper process cleanup to avoid orphans)
|
||||
t := tmux.NewTmux()
|
||||
sessionID := crewSessionName(r.Name, name)
|
||||
if hasSession, _ := t.HasSession(sessionID); hasSession {
|
||||
if err := t.KillSession(sessionID); err != nil {
|
||||
if err := t.KillSessionWithProcesses(sessionID); err != nil {
|
||||
fmt.Printf("Error killing session for %s: %v\n", arg, err)
|
||||
lastErr = err
|
||||
continue
|
||||
@@ -591,8 +591,8 @@ func runCrewStop(cmd *cobra.Command, args []string) error {
|
||||
output, _ = t.CapturePane(sessionID, 50)
|
||||
}
|
||||
|
||||
// Kill the session
|
||||
if err := t.KillSession(sessionID); err != nil {
|
||||
// Kill the session (with proper process cleanup to avoid orphans)
|
||||
if err := t.KillSessionWithProcesses(sessionID); err != nil {
|
||||
fmt.Printf(" %s [%s] %s: %s\n",
|
||||
style.ErrorPrefix,
|
||||
r.Name, name,
|
||||
@@ -681,8 +681,8 @@ func runCrewStopAll() error {
|
||||
output, _ = t.CapturePane(sessionID, 50)
|
||||
}
|
||||
|
||||
// Kill the session
|
||||
if err := t.KillSession(sessionID); err != nil {
|
||||
// Kill the session (with proper process cleanup to avoid orphans)
|
||||
if err := t.KillSessionWithProcesses(sessionID); err != nil {
|
||||
failed++
|
||||
failures = append(failures, fmt.Sprintf("%s: %v", agentName, err))
|
||||
fmt.Printf(" %s %s\n", style.ErrorPrefix, agentName)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -191,7 +191,8 @@ func FindOrphanedClaudeProcesses() ([]OrphanedProcess, error) {
|
||||
etimeStr := fields[3]
|
||||
|
||||
// Only look for claude/codex processes without a TTY
|
||||
if tty != "?" {
|
||||
// Linux shows "?" for no TTY, macOS shows "??"
|
||||
if tty != "?" && tty != "??" {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user