Files
gastown/internal/cmd/start_orphan_unix.go
Serhii a8be623eeb fix(cmd): extract orphan cleanup to platform-specific files (#835)
PR #759 introduced cleanupOrphanedClaude() using syscall.Kill directly,
which breaks Windows builds. This extracts the function to:
- start_orphan_unix.go: Full implementation with SIGTERM/SIGKILL
- start_orphan_windows.go: Stub (orphan signals not supported)

Follows existing pattern: process_unix.go / process_windows.go
2026-01-20 22:34:37 -08:00

89 lines
2.5 KiB
Go

//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("✓"))
}
}