diff --git a/internal/cmd/start.go b/internal/cmd/start.go index bfd7fb82..284891e9 100644 --- a/internal/cmd/start.go +++ b/internal/cmd/start.go @@ -9,7 +9,6 @@ import ( "strings" "sync" "sync/atomic" - "syscall" "time" "github.com/spf13/cobra" @@ -26,7 +25,6 @@ import ( "github.com/steveyegge/gastown/internal/session" "github.com/steveyegge/gastown/internal/style" "github.com/steveyegge/gastown/internal/tmux" - "github.com/steveyegge/gastown/internal/util" "github.com/steveyegge/gastown/internal/witness" "github.com/steveyegge/gastown/internal/workspace" ) @@ -1005,79 +1003,3 @@ func startCrewMember(rigName, crewName, townRoot string) error { return nil } - -// cleanupOrphanedClaude finds and kills orphaned Claude processes with a grace period. -// This is a simpler synchronous implementation that: -// 1. Finds orphaned processes (TTY-less, older than 60s, not in Gas Town sessions) -// 2. Sends SIGTERM to all of them -// 3. Waits for the grace period -// 4. Sends SIGKILL to any that are still alive -func cleanupOrphanedClaude(graceSecs int) { - // Find orphaned processes - orphans, err := util.FindOrphanedClaudeProcesses() - if err != nil { - fmt.Printf(" %s Warning: %v\n", style.Bold.Render("⚠"), err) - return - } - - if len(orphans) == 0 { - fmt.Printf(" %s No orphaned processes found\n", style.Dim.Render("○")) - return - } - - // Send SIGTERM to all orphans - var termPIDs []int - for _, orphan := range orphans { - if err := syscall.Kill(orphan.PID, syscall.SIGTERM); err != nil { - if err != syscall.ESRCH { - fmt.Printf(" %s PID %d: failed to send SIGTERM: %v\n", - style.Bold.Render("⚠"), orphan.PID, err) - } - continue - } - termPIDs = append(termPIDs, orphan.PID) - fmt.Printf(" %s PID %d: sent SIGTERM (waiting %ds before SIGKILL)\n", - style.Bold.Render("→"), orphan.PID, graceSecs) - } - - if len(termPIDs) == 0 { - return - } - - // Wait for grace period - fmt.Printf(" %s Waiting %d seconds for processes to terminate gracefully...\n", - style.Dim.Render("⏳"), graceSecs) - time.Sleep(time.Duration(graceSecs) * time.Second) - - // Check which processes are still alive and send SIGKILL - var killedCount, alreadyDeadCount int - for _, pid := range termPIDs { - // Check if process still exists - if err := syscall.Kill(pid, 0); err != nil { - // Process is gone (either died from SIGTERM or doesn't exist) - alreadyDeadCount++ - continue - } - - // Process still alive - send SIGKILL - if err := syscall.Kill(pid, syscall.SIGKILL); err != nil { - if err != syscall.ESRCH { - fmt.Printf(" %s PID %d: failed to send SIGKILL: %v\n", - style.Bold.Render("⚠"), pid, err) - } - continue - } - killedCount++ - fmt.Printf(" %s PID %d: sent SIGKILL (did not respond to SIGTERM)\n", - style.Bold.Render("✓"), pid) - } - - if alreadyDeadCount > 0 { - fmt.Printf(" %s %d process(es) terminated gracefully from SIGTERM\n", - style.Bold.Render("✓"), alreadyDeadCount) - } - if killedCount == 0 && alreadyDeadCount > 0 { - fmt.Printf(" %s All processes cleaned up successfully\n", - style.Bold.Render("✓")) - } -} diff --git a/internal/cmd/start_orphan_unix.go b/internal/cmd/start_orphan_unix.go new file mode 100644 index 00000000..3944e738 --- /dev/null +++ b/internal/cmd/start_orphan_unix.go @@ -0,0 +1,88 @@ +//go:build !windows + +package cmd + +import ( + "fmt" + "syscall" + "time" + + "github.com/steveyegge/gastown/internal/style" + "github.com/steveyegge/gastown/internal/util" +) + +// cleanupOrphanedClaude finds and kills orphaned Claude processes with a grace period. +// This is a simpler synchronous implementation that: +// 1. Finds orphaned processes (TTY-less, older than 60s, not in Gas Town sessions) +// 2. Sends SIGTERM to all of them +// 3. Waits for the grace period +// 4. Sends SIGKILL to any that are still alive +func cleanupOrphanedClaude(graceSecs int) { + // Find orphaned processes + orphans, err := util.FindOrphanedClaudeProcesses() + if err != nil { + fmt.Printf(" %s Warning: %v\n", style.Bold.Render("⚠"), err) + return + } + + if len(orphans) == 0 { + fmt.Printf(" %s No orphaned processes found\n", style.Dim.Render("○")) + return + } + + // Send SIGTERM to all orphans + var termPIDs []int + for _, orphan := range orphans { + if err := syscall.Kill(orphan.PID, syscall.SIGTERM); err != nil { + if err != syscall.ESRCH { + fmt.Printf(" %s PID %d: failed to send SIGTERM: %v\n", + style.Bold.Render("⚠"), orphan.PID, err) + } + continue + } + termPIDs = append(termPIDs, orphan.PID) + fmt.Printf(" %s PID %d: sent SIGTERM (waiting %ds before SIGKILL)\n", + style.Bold.Render("→"), orphan.PID, graceSecs) + } + + if len(termPIDs) == 0 { + return + } + + // Wait for grace period + fmt.Printf(" %s Waiting %d seconds for processes to terminate gracefully...\n", + style.Dim.Render("⏳"), graceSecs) + time.Sleep(time.Duration(graceSecs) * time.Second) + + // Check which processes are still alive and send SIGKILL + var killedCount, alreadyDeadCount int + for _, pid := range termPIDs { + // Check if process still exists + if err := syscall.Kill(pid, 0); err != nil { + // Process is gone (either died from SIGTERM or doesn't exist) + alreadyDeadCount++ + continue + } + + // Process still alive - send SIGKILL + if err := syscall.Kill(pid, syscall.SIGKILL); err != nil { + if err != syscall.ESRCH { + fmt.Printf(" %s PID %d: failed to send SIGKILL: %v\n", + style.Bold.Render("⚠"), pid, err) + } + continue + } + killedCount++ + fmt.Printf(" %s PID %d: sent SIGKILL (did not respond to SIGTERM)\n", + style.Bold.Render("✓"), pid) + } + + if alreadyDeadCount > 0 { + fmt.Printf(" %s %d process(es) terminated gracefully from SIGTERM\n", + style.Bold.Render("✓"), alreadyDeadCount) + } + if killedCount == 0 && alreadyDeadCount > 0 { + fmt.Printf(" %s All processes cleaned up successfully\n", + style.Bold.Render("✓")) + } +} diff --git a/internal/cmd/start_orphan_windows.go b/internal/cmd/start_orphan_windows.go new file mode 100644 index 00000000..39834e72 --- /dev/null +++ b/internal/cmd/start_orphan_windows.go @@ -0,0 +1,16 @@ +//go:build windows + +package cmd + +import ( + "fmt" + + "github.com/steveyegge/gastown/internal/style" +) + +// cleanupOrphanedClaude is a Windows stub. +// Orphan cleanup requires Unix-specific signals (SIGTERM/SIGKILL). +func cleanupOrphanedClaude(graceSecs int) { + fmt.Printf(" %s Orphan cleanup not supported on Windows\n", + style.Dim.Render("○")) +}