Skip to content

Skip directories when resolving provider executables on PATH - #9476

Merged
austinywang merged 2 commits into
mainfrom
issue-8743-cli-path-resolver-treats-directories-as
Aug 4, 2026
Merged

austinywang merged 2 commits into
mainfrom
issue-8743-cli-path-resolver-treats-directories-as

Conversation

@austinywang

@austinywang austinywang commented Aug 3, 2026 •

Copy link
Copy Markdown
Contributor

FileManager.isExecutableFile(atPath:) returns true for directories on macOS. The PATH walks that resolve provider binaries only called that API, so a directory named like the binary (~/bin/omx/, ~/bin/claude/) earlier on PATH was picked as the executable and the launch died at execv with Failed to launch omx: Permission denied.

Three PATH walks now reject directories with fileExists(atPath:isDirectory:) before the executable check, mirroring the guard resolveClaudeExecutable already applied to configured candidates:

  • CLI/CMUXCLI+ExecutableResolution.swift — resolveExecutableInSearchPath (claude, codex, opencode, omx, omc, auto-naming summarizers)
  • CLI/cmux.swift — resolveExecutableInPath (bun and the OMO/OMX/OMC helpers)
  • Sources/AgentExecutableResolver.swift — the app-side agent resolver, same bug

Fixes #8743

Testing

  • tests/test_issue_8743_path_directory_shadowing.py (new, wired into ci.yml): puts a directory named omx/omc ahead of a real executable on PATH and asserts the real one runs. Before the fix: FAIL ... Failed to launch omx: Permission denied for both. After: PASS.
  • cmuxTests/AgentExecutableResolverTests.swift — testSkipsDirectoryNamedLikeExecutableOnSearchPath covers the app-side resolver.
  • Red/green is split across the two commits: commit 1 adds the tests only, commit 2 adds the fix.
  • Dev build ./scripts/reload.sh --tag sym8743, then the issue's repro against the tagged CLI: with /tmp/.../shadow/omx (a directory) first on PATH, cmux omx --version now prints REAL OMX at /tmp/.../real/omx instead of failing. Tagged app killed afterwards.

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Summary by cubic

Skip directories when resolving provider executables on PATH so the CLI and app don’t select a directory named like the binary and fail to launch on macOS. Fixes #8743.

  • Bug Fixes
    • Reject directories before isExecutableFile in all PATH walks: CLI/CMUXCLI+ExecutableResolution.swift, CLI/cmux.swift, and Sources/AgentExecutableResolver.swift.
    • Added regression tests (tests/test_issue_8743_path_directory_shadowing.py, cmuxTests/AgentExecutableResolverTests.swift) and wired the new test into CI.

Written for commit 3e53303. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Bug Fixes

    • Improved command resolution to ignore directories that share a provider executable’s name.
    • Ensured the correct executable is selected from a later PATH entry when an earlier entry is shadowed by a directory.
  • Tests

    • Added regression coverage for PATH-based executable resolution across supported command-line interfaces.

austinywang and others added 2 commits August 3, 2026 14:52
FileManager.isExecutableFile(atPath:) returns true for directories on macOS,
so a directory named like a provider binary earlier on PATH is selected by the
CLI and app PATH walks. These tests fail until the resolvers reject directories.

Refs #8743

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
FileManager.isExecutableFile(atPath:) returns true for directories on macOS, so
a directory named like a provider binary (~/bin/omx/, ~/bin/claude/) earlier on
PATH was selected as the executable and the launch failed at execv with a
confusing "Permission denied". Reject directories with
fileExists(atPath:isDirectory:) before the executable check in all three PATH
walks, mirroring the guard resolveClaudeExecutable already applied to configured
candidates.

Fixes #8743

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 3, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4a12fc8b-30aa-49eb-9d9c-18785ee056f3

📥 Commits

Reviewing files that changed from the base of the PR and between be6516f and 3e53303.

📒 Files selected for processing (6)
  • .github/workflows/ci.yml
  • CLI/CMUXCLI+ExecutableResolution.swift
  • CLI/cmux.swift
  • Sources/AgentExecutableResolver.swift
  • cmuxTests/AgentExecutableResolverTests.swift
  • tests/test_issue_8743_path_directory_shadowing.py

📝 Walkthrough

Walkthrough

Executable resolution now skips directories that share provider executable names. Unit and CLI regression tests verify that later valid executables are selected, and CI runs the new CLI regression test.

Changes

PATH executable resolution

Layer / File(s) Summary
Reject directory candidates
CLI/CMUXCLI+ExecutableResolution.swift, CLI/cmux.swift, Sources/AgentExecutableResolver.swift
Executable lookup now requires existing non-directory candidates before checking executable permissions.
Validate PATH shadowing behavior
cmuxTests/AgentExecutableResolverTests.swift, tests/test_issue_8743_path_directory_shadowing.py, .github/workflows/ci.yml
Regression tests verify that directory candidates are skipped and valid executables from later PATH entries are selected. CI runs the new CLI regression test.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested reviewers: lawrencecchen, azooz2003-bit


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
Cmux Swift Package Boundaries ❌ Error AgentExecutableResolver in root Sources is injectable, Foundation-only, and unit-tested; the same PATH policy is duplicated in CLI and app without a SwiftPM boundary. Extract the shared regular-file PATH check into existing CMUXAgentLaunch. Expose a small public AgentExecutablePathResolver, and keep provider settings and launch composition in app and CLI.
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (23 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the primary change: skipping directories during provider executable resolution on PATH.
Description check ✅ Passed The description explains the cause, affected resolvers, regression tests, CI integration, and verification steps, with only non-critical template sections omitted.
Linked Issues check ✅ Passed The changes satisfy issue #8743 by rejecting directories before executable checks and adding regression coverage for PATH shadowing.
Out of Scope Changes check ✅ Passed All code and test changes support the PATH directory-shadowing fix and its regression coverage across the listed resolvers.
Cmux Swift Actor Isolation ✅ Passed The production diff only adds local directory checks around existing PATH resolution; it adds no actor, Sendable, protocol, UI-store, async, or MainActor isolation changes.
Cmux Swift Blocking Runtime ✅ Passed The production Swift diff only adds file-existence and directory checks; it introduces no semaphore, wait, sleep, timer, polling, main-queue sync, or manual lock.
Cmux Browser Automation Off-Main ✅ Passed The two-commit diff changes executable resolution, CI, and resolver tests only; no browser automation source, worker policy, or browser routing code changed.
Cmux Expensive Synchronous Load ✅ Passed The Swift diff only adds bounded PATH candidate existence/directory checks; it adds no agent-history load, large-file parse, directory scan, or transcript/session-store load.
Cmux Cache Substitution Correctness ✅ Passed The patch only adds fileExists/isDirectory guards to PATH executable resolution; it does not replace an authoritative read with a cache in persistence, history, undo, or snapshot paths.
Cmux No Hacky Sleeps ✅ Passed The only non-Swift addition is a deterministic Python regression test; its subprocess timeout is test scaffolding. CI YAML is out of scope, and no sleeps or polling were added.
Cmux Algorithmic Complexity ✅ Passed The production changes add directory checks inside existing linear PATH walks; they add no nested scans, per-target rescans, sorting, filtering, or scalable joins. Regression code is test-only.
Cmux Swift Concurrency ✅ Passed The diff only adds directory checks in PATH resolution plus regression tests and CI wiring; it introduces no Dispatch, Combine, completion-handler, or fire-and-forget Task patterns.
Cmux Swift @Concurrent ✅ Passed The PR adds only synchronous FileManager guards to existing resolver methods; no changed Swift line introduces async, @concurrent, nonisolated, or actor-isolation behavior.
Cmux Swiftpm Lockfiles ✅ Passed The PR changes only a workflow, Swift source, and tests; it changes no Package.swift, Package.resolved, .gitignore, or Xcode project references.
Cmux Swift Logging ✅ Passed The changed Swift hunks add only directory checks and comments; they add no print, debugPrint, dump, NSLog, file logging, Logger, or sensitive diagnostics. Test output is allowed.
Cmux User-Facing Error Privacy ✅ Passed Production hunks add only directory checks and developer comments; they add or alter no user-facing error, alert, command output, API body, or recovery copy.
Cmux Full Internationalization ✅ Passed The PR adds only executable-directory checks and developer comments in production Swift; test output and CI wiring are allowed, and no user-facing text or locale/catalog files changed.
Cmux Swiftui State Layout ✅ Passed The PR adds only executable-resolution guards and resolver tests; changed Swift files contain no SwiftUI views, observation state, GeometryReader, lazy rows, or render-time state mutation.
Cmux Architecture Rethink ✅ Passed The PR adds local directory guards before executable checks in three existing resolver owners. It adds no timing, mutable state, observers, locks, duplicate wiring, or UI lifecycle ownership.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed The PR changes only PATH executable resolvers and regression tests; it adds no NSWindow, NSPanel, NSWindowController, WindowGroup, or auxiliary-window shortcut code.
Cmux Source Artifacts ✅ Passed All six changed paths are intentional Swift source, CI configuration, and regression test files; no logs, caches, build output, temp directories, or copied artifacts appear in the diff.
Cmux No Test Or Debug Seam In Production Source ✅ Passed Sources/AgentExecutableResolver.swift only adds a directory guard to PATH resolution; it adds no test/debug guard, seam-named member, visibility widening, or test-only accessor.
Cmux No Ambient Global State ✅ Passed The PR adds only local isDirectory variables inside existing resolver methods; it adds no file-scope API, static namespace, singleton, or runtime global state.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-8743-cli-path-resolver-treats-directories-as

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@austinywang
austinywang merged commit 2039018 into main Aug 4, 2026
6 checks passed
@austinywang
austinywang deleted the issue-8743-cli-path-resolver-treats-directories-as branch August 4, 2026 01:28
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.

CLI PATH resolver treats directories as executables (isExecutableFile)

1 participant