Skip to content

Commit 4044b6d

Browse files
wesmclaude
andauthored
Honor *_cmd config values for agent availability checks (#495)
## Summary - Extract the inline command override switch in `GetAvailableWithConfig` into a reusable `applyCommandOverrides` helper that clones an agent and sets the configured command path - Add `isAvailableWithConfig` that checks agent availability using the configured command before falling back to the hardcoded default, so agents are found even when only the configured binary is in PATH - `GetAvailableWithConfig` now checks the preferred agent and backup agents with config-aware availability before falling through to the `GetAvailable` fallback chain - Add tests for `claude_code_cmd`, `codex_cmd`, `cursor_cmd`, and `pi_cmd` overrides, including the case where only the configured command is available Fixes #381 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 25d26ff commit 4044b6d

File tree

2 files changed

+386
-34
lines changed

2 files changed

+386
-34
lines changed

internal/agent/acp.go

Lines changed: 113 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1503,6 +1503,69 @@ func configuredACPAgent(cfg *config.Config) *ACPAgent {
15031503
return resolved
15041504
}
15051505

1506+
// applyCommandOverrides clones the agent and applies the configured
1507+
// command override from cfg. Returns the original agent unchanged when
1508+
// no override applies. Cloning avoids mutating global registry
1509+
// singletons that concurrent callers share.
1510+
func applyCommandOverrides(a Agent, cfg *config.Config) Agent {
1511+
if cfg == nil {
1512+
return a
1513+
}
1514+
switch agent := a.(type) {
1515+
case *CodexAgent:
1516+
if cfg.CodexCmd != "" {
1517+
clone := *agent
1518+
clone.Command = cfg.CodexCmd
1519+
return &clone
1520+
}
1521+
case *ClaudeAgent:
1522+
if cfg.ClaudeCodeCmd != "" {
1523+
clone := *agent
1524+
clone.Command = cfg.ClaudeCodeCmd
1525+
return &clone
1526+
}
1527+
case *CursorAgent:
1528+
if cfg.CursorCmd != "" {
1529+
clone := *agent
1530+
clone.Command = cfg.CursorCmd
1531+
return &clone
1532+
}
1533+
case *PiAgent:
1534+
if cfg.PiCmd != "" {
1535+
clone := *agent
1536+
clone.Command = cfg.PiCmd
1537+
return &clone
1538+
}
1539+
}
1540+
return a
1541+
}
1542+
1543+
// isAvailableWithConfig checks whether the named agent can be resolved
1544+
// to an executable command, considering config command overrides. If a
1545+
// config override points to an available binary, the agent is considered
1546+
// available even when the default command isn't in PATH.
1547+
func isAvailableWithConfig(name string, cfg *config.Config) bool {
1548+
name = resolveAlias(name)
1549+
a, ok := registry[name]
1550+
if !ok {
1551+
return false
1552+
}
1553+
ca, ok := a.(CommandAgent)
1554+
if !ok {
1555+
return true // non-command agents (e.g. test) are always available
1556+
}
1557+
// Check the configured command first — it takes priority.
1558+
overridden := applyCommandOverrides(a, cfg)
1559+
if oca, ok := overridden.(CommandAgent); ok {
1560+
if _, err := exec.LookPath(oca.CommandName()); err == nil {
1561+
return true
1562+
}
1563+
}
1564+
// Fall back to the default (hardcoded) command.
1565+
_, err := exec.LookPath(ca.CommandName())
1566+
return err == nil
1567+
}
1568+
15061569
// GetAvailableWithConfig resolves an available agent while honoring runtime ACP config.
15071570
// It treats cfg.ACP.Name as an alias for "acp" and applies cfg.ACP command/mode/model
15081571
// at resolution time instead of package-init time.
@@ -1528,10 +1591,58 @@ func GetAvailableWithConfig(preferred string, cfg *config.Config, backups ...str
15281591
}
15291592
}
15301593

1531-
// Finally fall back to normal auto-selection (with backups).
1594+
// ACP unavailable — try backup agents with config-aware
1595+
// availability so *_cmd overrides are honored.
1596+
if cfg != nil {
1597+
for _, b := range backups {
1598+
b = resolveAlias(b)
1599+
if b == "" {
1600+
continue
1601+
}
1602+
if _, ok := registry[b]; ok && isAvailableWithConfig(b, cfg) {
1603+
a, _ := Get(b)
1604+
return applyCommandOverrides(a, cfg), nil
1605+
}
1606+
}
1607+
}
1608+
1609+
// Finally fall back to normal auto-selection.
15321610
return GetAvailable("", backups...)
15331611
}
15341612

1613+
// Check the preferred agent using config command overrides before
1614+
// falling back. GetAvailable only checks the hardcoded default
1615+
// command via IsAvailable, so a configured command (e.g.
1616+
// claude_code_cmd = "/usr/local/bin/claude-wrapper") would be
1617+
// missed when the default binary isn't in PATH.
1618+
if preferred != "" && cfg != nil {
1619+
if _, ok := registry[preferred]; !ok {
1620+
// Unknown agent — let GetAvailable produce the error.
1621+
return GetAvailable(preferred, backups...)
1622+
}
1623+
if isAvailableWithConfig(preferred, cfg) {
1624+
a, _ := Get(preferred)
1625+
return applyCommandOverrides(a, cfg), nil
1626+
}
1627+
}
1628+
1629+
// Try backup agents with config-aware availability before the
1630+
// fallback chain. This runs regardless of whether preferred is
1631+
// set so that backup-only configurations (preferred="" with a
1632+
// backup_agent) still honor *_cmd overrides.
1633+
if cfg != nil {
1634+
for _, b := range backups {
1635+
b = resolveAlias(b)
1636+
if b == "" || b == preferred {
1637+
continue
1638+
}
1639+
if _, ok := registry[b]; ok && isAvailableWithConfig(b, cfg) {
1640+
a, _ := Get(b)
1641+
return applyCommandOverrides(a, cfg), nil
1642+
}
1643+
}
1644+
}
1645+
15351646
resolved, err := GetAvailable(preferred, backups...)
15361647
if err != nil {
15371648
return nil, err
@@ -1544,39 +1655,7 @@ func GetAvailableWithConfig(preferred string, cfg *config.Config, backups ...str
15441655
return resolved, nil
15451656
}
15461657

1547-
// Apply command overrides from config.
1548-
// Clone agent instances before mutating to avoid contaminating the
1549-
// global registry — concurrent callers share those singletons.
1550-
if cfg != nil {
1551-
switch agent := resolved.(type) {
1552-
case *CodexAgent:
1553-
if cfg.CodexCmd != "" {
1554-
clone := *agent
1555-
clone.Command = cfg.CodexCmd
1556-
resolved = &clone
1557-
}
1558-
case *ClaudeAgent:
1559-
if cfg.ClaudeCodeCmd != "" {
1560-
clone := *agent
1561-
clone.Command = cfg.ClaudeCodeCmd
1562-
resolved = &clone
1563-
}
1564-
case *CursorAgent:
1565-
if cfg.CursorCmd != "" {
1566-
clone := *agent
1567-
clone.Command = cfg.CursorCmd
1568-
resolved = &clone
1569-
}
1570-
case *PiAgent:
1571-
if cfg.PiCmd != "" {
1572-
clone := *agent
1573-
clone.Command = cfg.PiCmd
1574-
resolved = &clone
1575-
}
1576-
}
1577-
}
1578-
1579-
return resolved, nil
1658+
return applyCommandOverrides(resolved, cfg), nil
15801659
}
15811660

15821661
func applyACPAgentConfigOverride(cfg *config.ACPAgentConfig, override *config.ACPAgentConfig) {

0 commit comments

Comments
 (0)