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 <noreply@anthropic.com>
This commit is contained in:
gastown/crew/george
2026-01-09 22:08:12 -08:00
committed by Steve Yegge
parent e0858096f6
commit c94d59dca7
6 changed files with 16 additions and 46 deletions

View File

@@ -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 {

View File

@@ -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/<worker>/<issue> 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]

View File

@@ -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
}

View File

@@ -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

View File

@@ -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))
}
}

View File

@@ -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)