Fix gt doctor hanging on large workspaces

Two fixes:

1. FindAllLocks in lock/lock.go was walking into massive git repo
   clones (e.g., 13GB java repos in crew members). Added skipDirs map
   to skip .git, node_modules, vendor, target, build, and other large
   irrelevant directories during filepath.Walk.

2. CloneDivergenceCheck's git fetch could hang indefinitely waiting
   for SSH authentication prompts. Added environment variables to
   disable interactive SSH features:
   - GIT_SSH_COMMAND='ssh -o BatchMode=yes -o ConnectTimeout=3'
   - GIT_TERMINAL_PROMPT=0

Doctor now completes in ~2 minutes instead of hanging indefinitely.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-17 00:44:29 -08:00
committed by John Ogle
parent f97f3f6934
commit 3ffebb136f
2 changed files with 28 additions and 3 deletions

View File

@@ -534,10 +534,16 @@ func (c *CloneDivergenceCheck) getCloneInfo(path string) (cloneInfo, error) {
// Fetch to make sure we have latest refs (silent, ignore errors)
// Use a short timeout to prevent hanging on network issues, SSH prompts, or credential dialogs
// Also disable SSH interactive features to prevent blocking on password/passphrase prompts
fetchCtx, fetchCancel := context.WithTimeout(context.Background(), 5*time.Second)
defer fetchCancel()
cmd = exec.CommandContext(fetchCtx, "git", "fetch", "--quiet")
cmd.Dir = path
// Disable SSH password prompts and batch mode - fail fast instead of hanging
cmd.Env = append(os.Environ(),
"GIT_SSH_COMMAND=ssh -o BatchMode=yes -o ConnectTimeout=3",
"GIT_TERMINAL_PROMPT=0",
)
_ = cmd.Run()
// Count commits behind origin/main

View File

@@ -28,10 +28,10 @@ var (
// LockInfo contains information about who holds a lock.
type LockInfo struct {
PID int `json:"pid"`
PID int `json:"pid"`
AcquiredAt time.Time `json:"acquired_at"`
SessionID string `json:"session_id,omitempty"`
Hostname string `json:"hostname,omitempty"`
SessionID string `json:"session_id,omitempty"`
Hostname string `json:"hostname,omitempty"`
}
// IsStale checks if the lock is stale (owning process is dead).
@@ -194,15 +194,34 @@ func (l *Lock) write(sessionID string) error {
// FindAllLocks scans a directory tree for agent.lock files.
// Returns a map of worker directory -> LockInfo.
// Skips .git directories and other large irrelevant directories for performance.
func FindAllLocks(root string) (map[string]*LockInfo, error) {
locks := make(map[string]*LockInfo)
// Directories to skip during walk (these won't contain .runtime/agent.lock files)
skipDirs := map[string]bool{
".git": true,
"node_modules": true,
"vendor": true,
"target": true, // Rust/Java build output
"build": true,
"dist": true,
".gradle": true,
".m2": true,
"bazel-out": true,
"bazel-bin": true,
}
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return nil // Skip errors
}
if info.IsDir() {
// Skip irrelevant directories for performance
if skipDirs[info.Name()] {
return filepath.SkipDir
}
return nil
}