fix(dispatcher): resolve bare claude binary name at construction time#16
Merged
Merged
Conversation
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.
This was referenced Apr 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Under the launchd-managed poller, PATH does not include
~/.local/bin, so spawning claude fails with:The previous launchd fix (0c7c4e5) added
_find_claude()as the dataclass field'sdefault_factory, but the CLI always passesclaude_binary=config.claude.binaryexplicitly (seecli.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 toasyncio.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 viashutil.whichfirst 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 passedpytest -q— 146 passedNo such file or directoryerror