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
This commit is contained in:
@@ -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("✓"))
|
||||
}
|
||||
}
|
||||
|
||||
88
internal/cmd/start_orphan_unix.go
Normal file
88
internal/cmd/start_orphan_unix.go
Normal file
@@ -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("✓"))
|
||||
}
|
||||
}
|
||||
16
internal/cmd/start_orphan_windows.go
Normal file
16
internal/cmd/start_orphan_windows.go
Normal file
@@ -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("○"))
|
||||
}
|
||||
Reference in New Issue
Block a user