From c94d59dca76419e1620c9e6ce63a406476852aab Mon Sep 17 00:00:00 2001 From: gastown/crew/george Date: Fri, 9 Jan 2026 22:08:12 -0800 Subject: [PATCH] fix: ZFC improvements - query tmux directly instead of marker TTL Two ZFC fixes: 1. Boot marker file (hq-zee5n): Changed IsRunning() to query tmux.HasSession() directly instead of checking marker file freshness with TTL. Removed stale marker check from doctor. 2. Branch pattern matching (hq-zwuh6): Replaced hardcoded "polecat/" strings with constants.BranchPolecatPrefix for consistency. Also removed 60-second WaitForCommand blocking from crew Start() which was causing gt crew start to hang. Co-Authored-By: Claude Opus 4.5 --- internal/boot/boot.go | 24 ++++-------------------- internal/cmd/mq_submit.go | 3 ++- internal/crew/manager.go | 7 ++++--- internal/doctor/boot_check.go | 22 +++------------------- internal/doctor/rig_check.go | 4 ++-- internal/refinery/manager.go | 2 +- 6 files changed, 16 insertions(+), 46 deletions(-) diff --git a/internal/boot/boot.go b/internal/boot/boot.go index 5754ed06..af4350aa 100644 --- a/internal/boot/boot.go +++ b/internal/boot/boot.go @@ -23,15 +23,12 @@ import ( // to return true when only Boot is running. const SessionName = "gt-boot" -// MarkerFileName is the file that indicates Boot is currently running. +// MarkerFileName is the lock file for Boot startup coordination. const MarkerFileName = ".boot-running" // StatusFileName stores Boot's last execution status. const StatusFileName = ".boot-status.json" -// DefaultMarkerTTL is how long a marker is considered valid before it's stale. -const DefaultMarkerTTL = 5 * time.Minute - // Status represents Boot's execution status. type Status struct { Running bool `json:"running"` @@ -78,22 +75,9 @@ func (b *Boot) statusPath() string { } // IsRunning checks if Boot is currently running. -// Returns true if marker exists and isn't stale, false otherwise. +// Queries tmux directly for observable reality (ZFC principle). func (b *Boot) IsRunning() bool { - info, err := os.Stat(b.markerPath()) - if err != nil { - return false - } - - // Check if marker is stale (older than TTL) - age := time.Since(info.ModTime()) - if age > DefaultMarkerTTL { - // Stale marker - clean it up - _ = os.Remove(b.markerPath()) - return false - } - - return true + return b.IsSessionAlive() } // IsSessionAlive checks if the Boot tmux session exists. @@ -106,7 +90,7 @@ func (b *Boot) IsSessionAlive() bool { // Returns error if Boot is already running. func (b *Boot) AcquireLock() error { if b.IsRunning() { - return fmt.Errorf("boot is already running (marker exists)") + return fmt.Errorf("boot is already running (session exists)") } if err := b.EnsureDir(); err != nil { diff --git a/internal/cmd/mq_submit.go b/internal/cmd/mq_submit.go index f453dc3d..89d8bd5c 100644 --- a/internal/cmd/mq_submit.go +++ b/internal/cmd/mq_submit.go @@ -11,6 +11,7 @@ import ( "github.com/spf13/cobra" "github.com/steveyegge/gastown/internal/beads" + "github.com/steveyegge/gastown/internal/constants" "github.com/steveyegge/gastown/internal/git" "github.com/steveyegge/gastown/internal/rig" "github.com/steveyegge/gastown/internal/style" @@ -32,7 +33,7 @@ func parseBranchName(branch string) branchInfo { info := branchInfo{Branch: branch} // Try polecat// format - if strings.HasPrefix(branch, "polecat/") { + if strings.HasPrefix(branch, constants.BranchPolecatPrefix) { parts := strings.SplitN(branch, "/", 3) if len(parts) == 3 { info.Worker = parts[1] diff --git a/internal/crew/manager.go b/internal/crew/manager.go index 312764a3..e2cb6a25 100644 --- a/internal/crew/manager.go +++ b/internal/crew/manager.go @@ -13,7 +13,6 @@ import ( "github.com/steveyegge/gastown/internal/beads" "github.com/steveyegge/gastown/internal/claude" "github.com/steveyegge/gastown/internal/config" - "github.com/steveyegge/gastown/internal/constants" "github.com/steveyegge/gastown/internal/git" "github.com/steveyegge/gastown/internal/rig" "github.com/steveyegge/gastown/internal/session" @@ -536,8 +535,10 @@ func (m *Manager) Start(name string, opts StartOptions) error { // Set up C-b n/p keybindings for crew session cycling (non-fatal) _ = t.SetCrewCycleBindings(sessionID) - // Wait for Claude to start (non-fatal: session continues even if this times out) - _ = t.WaitForCommand(sessionID, constants.SupportedShells, constants.ClaudeStartTimeout) + // Note: We intentionally don't wait for Claude to start here. + // The session is created in detached mode, and blocking for 60 seconds + // serves no purpose. If the caller needs to know when Claude is ready, + // they can check with IsClaudeRunning(). return nil } diff --git a/internal/doctor/boot_check.go b/internal/doctor/boot_check.go index 31202088..7f620ac2 100644 --- a/internal/doctor/boot_check.go +++ b/internal/doctor/boot_check.go @@ -3,7 +3,6 @@ package doctor import ( "fmt" "os" - "path/filepath" "time" "github.com/steveyegge/gastown/internal/boot" @@ -84,24 +83,9 @@ func (c *BootHealthCheck) Run(ctx *CheckContext) *CheckResult { details = append(details, "No previous run recorded") } - // Check 4: Marker file freshness (stale marker indicates crash) - markerPath := filepath.Join(bootDir, boot.MarkerFileName) - if info, err := os.Stat(markerPath); err == nil { - age := time.Since(info.ModTime()) - if age > boot.DefaultMarkerTTL { - return &CheckResult{ - Name: c.Name(), - Status: StatusWarning, - Message: "Boot marker is stale (possible crash)", - Details: []string{ - fmt.Sprintf("Marker age: %s", age.Round(time.Second)), - fmt.Sprintf("TTL: %s", boot.DefaultMarkerTTL), - }, - FixHint: "Stale marker will be cleaned on next daemon tick", - } - } - // Marker exists and is fresh - Boot is currently running - details = append(details, fmt.Sprintf("Currently running (marker age: %s)", age.Round(time.Second))) + // Check 4: Currently running (uses tmux session state per ZFC principle) + if sessionAlive { + details = append(details, "Currently running (tmux session active)") } // All checks passed diff --git a/internal/doctor/rig_check.go b/internal/doctor/rig_check.go index ed5fd0eb..3ca4e66f 100644 --- a/internal/doctor/rig_check.go +++ b/internal/doctor/rig_check.go @@ -742,8 +742,8 @@ func (c *PolecatClonesValidCheck) Run(ctx *CheckContext) *CheckResult { branchOutput, err := cmd.Output() if err == nil { branch := strings.TrimSpace(string(branchOutput)) - if !strings.HasPrefix(branch, "polecat/") { - warnings = append(warnings, fmt.Sprintf("%s: on branch '%s' (expected polecat/*)", polecatName, branch)) + if !strings.HasPrefix(branch, constants.BranchPolecatPrefix) { + warnings = append(warnings, fmt.Sprintf("%s: on branch '%s' (expected %s*)", polecatName, branch, constants.BranchPolecatPrefix)) } } diff --git a/internal/refinery/manager.go b/internal/refinery/manager.go index fd87486a..4ff6f647 100644 --- a/internal/refinery/manager.go +++ b/internal/refinery/manager.go @@ -663,7 +663,7 @@ func (m *Manager) FindMR(idOrBranch string) (*MergeRequest, error) { if item.MR.Branch == idOrBranch { return item.MR, nil } - if "polecat/"+idOrBranch == item.MR.Branch { + if constants.BranchPolecatPrefix+idOrBranch == item.MR.Branch { return item.MR, nil } // Match by worker name (partial match for convenience)