fix(tmux): resolve claude path for alias installations (#703) (#748)

Fix "Unable to attach mayor" timeout caused by claude being installed
as a shell alias rather than in PATH. Non-interactive shells spawned
by tmux cannot resolve aliases, causing the session to exit immediately.

Changes:
- Add resolveClaudePath() to find claude at ~/.claude/local/claude
- Apply path resolution in RuntimeConfigFromPreset() for claude preset
- Make hasClaudeChild() recursive (now hasClaudeDescendant()) to search
  entire process subtree as defensive improvement
- Update fillRuntimeDefaults() to use DefaultRuntimeConfig() for
  consistent path resolution

Fixes https://github.com/steveyegge/gastown/issues/703

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Kartik Shrivastava
2026-01-21 12:02:07 +05:30
committed by John Ogle
parent 858782d657
commit 123c7407fc
4 changed files with 167 additions and 70 deletions

View File

@@ -2,8 +2,9 @@
package config
import (
"path/filepath"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
)
@@ -469,10 +470,35 @@ func defaultRuntimeCommand(provider string) string {
case "generic":
return ""
default:
return "claude"
return resolveClaudePath()
}
}
// resolveClaudePath finds the claude binary, checking PATH first then common installation locations.
// This handles the case where claude is installed as an alias (not in PATH) which doesn't work
// in non-interactive shells spawned by tmux.
func resolveClaudePath() string {
// First, try to find claude in PATH
if path, err := exec.LookPath("claude"); err == nil {
return path
}
// Check common Claude Code installation locations
home, err := os.UserHomeDir()
if err != nil {
return "claude" // Fall back to bare command
}
// Standard Claude Code installation path
claudePath := filepath.Join(home, ".claude", "local", "claude")
if _, err := os.Stat(claudePath); err == nil {
return claudePath
}
// Fall back to bare command (might work if PATH is set differently in tmux)
return "claude"
}
func defaultRuntimeArgs(provider string) []string {
switch provider {
case "claude":