fix(doctor): make orphan process check informational only
The orphan-processes check previously killed any Claude process without a tmux ancestor, which incorrectly targeted user's personal Claude sessions running in regular terminals. Now the check is informational only: - Changed from FixableCheck to BaseCheck (no auto-fix) - Returns StatusOK with details listing processes outside tmux - Message advises user to verify processes are expected - Removed Fix method and related helpers The orphan-sessions check remains fixable since it only targets gt-* sessions that don't match valid Gas Town patterns. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
134
internal/doctor/orphan_check_test.go
Normal file
134
internal/doctor/orphan_check_test.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package doctor
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewOrphanSessionCheck(t *testing.T) {
|
||||
check := NewOrphanSessionCheck()
|
||||
|
||||
if check.Name() != "orphan-sessions" {
|
||||
t.Errorf("expected name 'orphan-sessions', got %q", check.Name())
|
||||
}
|
||||
|
||||
if !check.CanFix() {
|
||||
t.Error("expected CanFix to return true for session check")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewOrphanProcessCheck(t *testing.T) {
|
||||
check := NewOrphanProcessCheck()
|
||||
|
||||
if check.Name() != "orphan-processes" {
|
||||
t.Errorf("expected name 'orphan-processes', got %q", check.Name())
|
||||
}
|
||||
|
||||
// OrphanProcessCheck should NOT be fixable - it's informational only
|
||||
if check.CanFix() {
|
||||
t.Error("expected CanFix to return false for process check (informational only)")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrphanProcessCheck_Run(t *testing.T) {
|
||||
// This test verifies the check runs without error.
|
||||
// Results depend on whether Claude processes exist in the test environment.
|
||||
check := NewOrphanProcessCheck()
|
||||
ctx := &CheckContext{TownRoot: t.TempDir()}
|
||||
|
||||
result := check.Run(ctx)
|
||||
|
||||
// Should return OK (no processes or all inside tmux) or Warning (processes outside tmux)
|
||||
// Both are valid depending on test environment
|
||||
if result.Status != StatusOK && result.Status != StatusWarning {
|
||||
t.Errorf("expected StatusOK or StatusWarning, got %v: %s", result.Status, result.Message)
|
||||
}
|
||||
|
||||
// If warning, should have informational details
|
||||
if result.Status == StatusWarning {
|
||||
if len(result.Details) < 3 {
|
||||
t.Errorf("expected at least 3 detail lines (2 info + 1 process), got %d", len(result.Details))
|
||||
}
|
||||
// Should NOT have a FixHint since this is informational only
|
||||
if result.FixHint != "" {
|
||||
t.Errorf("expected no FixHint for informational check, got %q", result.FixHint)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrphanProcessCheck_MessageContent(t *testing.T) {
|
||||
// Verify the check description is correct
|
||||
check := NewOrphanProcessCheck()
|
||||
|
||||
expectedDesc := "Detect Claude processes outside tmux"
|
||||
if check.Description() != expectedDesc {
|
||||
t.Errorf("expected description %q, got %q", expectedDesc, check.Description())
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsCrewSession(t *testing.T) {
|
||||
tests := []struct {
|
||||
session string
|
||||
want bool
|
||||
}{
|
||||
{"gt-gastown-crew-joe", true},
|
||||
{"gt-beads-crew-max", true},
|
||||
{"gt-rig-crew-a", true},
|
||||
{"gt-gastown-witness", false},
|
||||
{"gt-gastown-refinery", false},
|
||||
{"gt-gastown-polecat1", false},
|
||||
{"hq-deacon", false},
|
||||
{"hq-mayor", false},
|
||||
{"other-session", false},
|
||||
{"gt-crew", false}, // Not enough parts
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.session, func(t *testing.T) {
|
||||
got := isCrewSession(tt.session)
|
||||
if got != tt.want {
|
||||
t.Errorf("isCrewSession(%q) = %v, want %v", tt.session, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrphanSessionCheck_IsValidSession(t *testing.T) {
|
||||
check := NewOrphanSessionCheck()
|
||||
validRigs := []string{"gastown", "beads"}
|
||||
mayorSession := "hq-mayor"
|
||||
deaconSession := "hq-deacon"
|
||||
|
||||
tests := []struct {
|
||||
session string
|
||||
want bool
|
||||
}{
|
||||
// Town-level sessions
|
||||
{"hq-mayor", true},
|
||||
{"hq-deacon", true},
|
||||
|
||||
// Valid rig sessions
|
||||
{"gt-gastown-witness", true},
|
||||
{"gt-gastown-refinery", true},
|
||||
{"gt-gastown-polecat1", true},
|
||||
{"gt-beads-witness", true},
|
||||
{"gt-beads-refinery", true},
|
||||
{"gt-beads-crew-max", true},
|
||||
|
||||
// Invalid rig sessions (rig doesn't exist)
|
||||
{"gt-unknown-witness", false},
|
||||
{"gt-foo-refinery", false},
|
||||
|
||||
// Non-gt sessions (should not be checked by this function,
|
||||
// but if called, they'd fail format validation)
|
||||
{"other-session", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.session, func(t *testing.T) {
|
||||
got := check.isValidSession(tt.session, validRigs, mayorSession, deaconSession)
|
||||
if got != tt.want {
|
||||
t.Errorf("isValidSession(%q) = %v, want %v", tt.session, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user