Skip to content

fix(dispatcher): resolve bare claude binary name at construction time#16

Merged
oscarvalenzuelab merged 2 commits into
mainfrom
fix/dispatcher-resolve-bare-claude
Apr 18, 2026
Merged

fix(dispatcher): resolve bare claude binary name at construction time#16
oscarvalenzuelab merged 2 commits into
mainfrom
fix/dispatcher-resolve-bare-claude

Conversation

@oscarvalenzuelab
Copy link
Copy Markdown
Collaborator

Summary

Under the launchd-managed poller, PATH does not include ~/.local/bin, so spawning claude fails with:

❌ Failed on #15: [Errno 2] No such file or directory: 'claude'

The previous launchd fix (0c7c4e5) added _find_claude() as the dataclass field's default_factory, but the CLI always passes claude_binary=config.claude.binary explicitly (see cli.py:478, cli.py:593, cli.py:787). The config default is the bare name "claude", so the factory is bypassed and the bare name is handed straight to asyncio.create_subprocess_exec, which cannot resolve it under launchd's minimal PATH.

Fix

In ClaudeDispatcher.__post_init__, if the provided binary is not an absolute path, resolve it via shutil.which first and fall back to the common-locations search (_find_claude) if PATH lookup fails. Works whether the caller supplies a bare name, an absolute path, or nothing.

Test plan

  • pytest tests/test_dispatcher.py -q — 4 passed
  • Full suite: pytest -q — 146 passed
  • After merge, reload the poller plist and retrigger a new issue — expect the agent to pick it up without the No such file or directory error

Issue: under the launchd-managed poller, PATH does not include
~/.local/bin, so spawning claude failed with:

    [Errno 2] No such file or directory: 'claude'

The previous fix added _find_claude() as the dataclass field's
default_factory, but the CLI always passes claude_binary=config.claude.binary
explicitly. Since the config default is the bare name "claude", the factory
was bypassed and the bare name was handed straight to asyncio.create_subprocess_exec,
which cannot resolve it under launchd's minimal PATH.

Fix: in __post_init__, if the provided binary is not an absolute path,
resolve it via shutil.which first and fall back to the common-locations
search (_find_claude) if PATH lookup fails. This works whether the caller
supplies a bare name, an absolute path, or nothing.
Address codex review P2s on PR #16:

- Previously __post_init__ rewrote every non-absolute claude_binary, which
  broke worktree-relative wrappers like ./tools/claude-wrapper (they used
  to be resolved by the child against working_dir).
- It also silently swapped a misconfigured custom binary for the stock
  claude via _find_claude(), masking config typos instead of failing fast.

Narrow the auto-resolve to only trigger when the value is literally the
config default "claude". Absolute paths, relative paths, and custom bare
names pass through unchanged. The launchd fix still works because the
config default is "claude", which is the only case we need to resolve.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant