Fix orphan detection to recognize hq-* sessions (#744)
The daemon creates hq-deacon and hq-mayor sessions (headquarters sessions) that were incorrectly flagged as orphaned by gt doctor. Changes: - Update orphan session check to recognize hq-* prefix in addition to gt-* - Update orphan process check to detect 'tmux: server' process name on Linux - Add test coverage for hq-* session validation - Update documentation comments to reflect hq-* patterns This fixes the false positive warnings where hq-deacon session and its child processes were incorrectly reported as orphaned. Co-authored-by: Roland Tritsch <roland@ailtir.com> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -94,8 +94,8 @@ func (c *OrphanSessionCheck) Run(ctx *CheckContext) *CheckResult {
|
||||
continue
|
||||
}
|
||||
|
||||
// Only check gt-* sessions (Gas Town sessions)
|
||||
if !strings.HasPrefix(sess, "gt-") {
|
||||
// Only check gt-* and hq-* sessions (Gas Town sessions)
|
||||
if !strings.HasPrefix(sess, "gt-") && !strings.HasPrefix(sess, "hq-") {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -200,8 +200,8 @@ func (c *OrphanSessionCheck) getValidRigs(townRoot string) []string {
|
||||
|
||||
// isValidSession checks if a session name matches expected Gas Town patterns.
|
||||
// Valid patterns:
|
||||
// - gt-{town}-mayor (dynamic based on town name)
|
||||
// - gt-{town}-deacon (dynamic based on town name)
|
||||
// - hq-mayor (headquarters mayor session)
|
||||
// - hq-deacon (headquarters deacon session)
|
||||
// - gt-<rig>-witness
|
||||
// - gt-<rig>-refinery
|
||||
// - gt-<rig>-<polecat> (where polecat is any name)
|
||||
@@ -354,8 +354,9 @@ func (c *OrphanProcessCheck) getTmuxSessionPIDs() (map[int]bool, error) { //noli
|
||||
|
||||
// Find tmux server processes using ps instead of pgrep.
|
||||
// pgrep -x tmux is unreliable on macOS - it often misses the actual server.
|
||||
// We use ps with awk to find processes where comm is exactly "tmux".
|
||||
out, err := exec.Command("sh", "-c", `ps ax -o pid,comm | awk '$2 == "tmux" || $2 ~ /\/tmux$/ { print $1 }'`).Output()
|
||||
// We use ps with awk to find processes where comm is exactly "tmux" or starts with "tmux:".
|
||||
// On Linux, tmux servers show as "tmux: server" in the comm field.
|
||||
out, err := exec.Command("sh", "-c", `ps ax -o pid,comm | awk '$2 == "tmux" || $2 ~ /\/tmux$/ || $2 ~ /^tmux:/ { print $1 }'`).Output()
|
||||
if err != nil {
|
||||
// No tmux server running
|
||||
return pids, nil
|
||||
|
||||
@@ -358,6 +358,37 @@ func TestIsCrewSession_ComprehensivePatterns(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestOrphanSessionCheck_HQSessions tests that hq-* sessions are properly recognized as valid.
|
||||
func TestOrphanSessionCheck_HQSessions(t *testing.T) {
|
||||
townRoot := t.TempDir()
|
||||
mayorDir := filepath.Join(townRoot, "mayor")
|
||||
if err := os.MkdirAll(mayorDir, 0o755); err != nil {
|
||||
t.Fatalf("create mayor dir: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(mayorDir, "rigs.json"), []byte("{}"), 0o644); err != nil {
|
||||
t.Fatalf("create rigs.json: %v", err)
|
||||
}
|
||||
|
||||
lister := &mockSessionLister{
|
||||
sessions: []string{
|
||||
"hq-mayor", // valid: headquarters mayor session
|
||||
"hq-deacon", // valid: headquarters deacon session
|
||||
},
|
||||
}
|
||||
check := NewOrphanSessionCheckWithSessionLister(lister)
|
||||
result := check.Run(&CheckContext{TownRoot: townRoot})
|
||||
|
||||
if result.Status != StatusOK {
|
||||
t.Fatalf("expected StatusOK for valid hq sessions, got %v: %s", result.Status, result.Message)
|
||||
}
|
||||
if result.Message != "All 2 Gas Town sessions are valid" {
|
||||
t.Fatalf("unexpected message: %q", result.Message)
|
||||
}
|
||||
if len(check.orphanSessions) != 0 {
|
||||
t.Fatalf("expected no orphan sessions, got %v", check.orphanSessions)
|
||||
}
|
||||
}
|
||||
|
||||
// TestOrphanSessionCheck_Run_Deterministic tests the full Run path with a mock session
|
||||
// lister, ensuring deterministic behavior without depending on real tmux state.
|
||||
func TestOrphanSessionCheck_Run_Deterministic(t *testing.T) {
|
||||
@@ -383,9 +414,11 @@ func TestOrphanSessionCheck_Run_Deterministic(t *testing.T) {
|
||||
"gt-gastown-witness", // valid: gastown rig exists
|
||||
"gt-gastown-polecat1", // valid: gastown rig exists
|
||||
"gt-beads-refinery", // valid: beads rig exists
|
||||
"hq-mayor", // valid: hq-mayor is recognized
|
||||
"hq-deacon", // valid: hq-deacon is recognized
|
||||
"gt-unknown-witness", // orphan: unknown rig doesn't exist
|
||||
"gt-missing-crew-joe", // orphan: missing rig doesn't exist
|
||||
"random-session", // ignored: doesn't match gt-* pattern
|
||||
"random-session", // ignored: doesn't match gt-*/hq-* pattern
|
||||
},
|
||||
}
|
||||
check := NewOrphanSessionCheckWithSessionLister(lister)
|
||||
|
||||
Reference in New Issue
Block a user