Repository navigation
fix(binaries): ignore tool binaries found via the current directory - #4838
chopratejas wants to merge 5 commits into
Conversation
which(), resolve() and status() took shutil.which()'s first hit. On Windows that searches the current directory before PATH, and a relative PATH entry such as "." does the same elsewhere, so a same-named file in the project being worked on was used instead of the real or cached tool. Such hits are now ignored and lookup falls back to the interpreter's Scripts dir and the SHA-256-verified cache. Assay-Kind: bugfix Assay-Ask: Headroom's tool lookup could pick up an ast-grep/difft/scc binary from the current working directory before the pinned cached one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PR governanceThis PR does not yet satisfy the required template fields:
Please update the PR body, or move the PR back to draft while it is still in progress. |
resolve() runs for every tool result in the proxy, so the warning about an ignored tool in the current directory repeated on every request. Assay-Kind: bugfix Assay-Ask: The warning about ignoring a tool binary in the current directory repeated on every proxied tool result. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
… hit shutil.which() returns only the first match, so skipping a hit in the current directory hid a tool installed in a later PATH entry. The lookup now continues through the absolute PATH entries. status() uses the same _path_lookup() as which()/resolve(), so doctor also sees tools in the interpreter's Scripts dir. Assay-Kind: bugfix Assay-Ask: Skipping a current-directory tool hit hid the same tool installed later on PATH, and doctor ignored the interpreter's Scripts dir. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Picks up the dependency updates from #4835 so the audit checks pass. Assay-Kind: deps Assay-Ask: The dependency audit checks failed on this PR because the branch predates the dependency updates on main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…Scripts dir The Scripts-dir fallback in _path_lookup checked only that the path existed, so which(), resolve() and status() accepted a non-executable file or a directory there. It now requires a regular file with execute permission, the same test shutil.which applies. Assay-Kind: bugfix Assay-Ask: The interpreter Scripts-dir fallback accepted a non-executable file as an installed tool. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
JerrettDavis
left a comment
There was a problem hiding this comment.
The PATH-continuation fix still fails on Windows: shutil.which(name, path=entry) can prepend the current directory again. With project/difft.exe and an installed difft.exe in an absolute PATH directory, each retry returns the project executable and is discarded; with an empty cache/interpreter directory, offline resolve fails and doctor reports missing instead of using the installed tool. A CPython 3.11 Windows-branch probe reproduced helper=None with the shadow present and the installed path after removing it (simulated Windows branch on Linux, not a Windows-host test). Please search explicit absolute candidates without the implicit cwd lookup, preserve PATHEXT handling, and add a Windows regression for this case. The earlier three discussion findings are resolved; this is materially new Windows evidence for the continuation case. Hosted CI additionally reports commitlint failure; please resolve that required check. Inherited workflow provenance concerns are tracked separately and are not attributed to this PR.
Description
headroom.binaries._path_lookup()(used bywhich(),resolve()andensure_tools()) andstatus()returnedshutil.which(name)before looking at the SHA-256-pinned cache. On Windowsshutil.whichsearches the current directory before PATH (unlessNoDefaultCurrentDirectoryInExePathis set), and on any OS a relative PATH entry (., an empty entry,bin) resolves against the cwd. The cwd is usually the project being worked on, so a file there namedast-grep,difft,scc, ... was run in place of the real tool, and the cache's pin never applied to it.A PATH hit is now ignored when it comes from a relative PATH entry or lives in the current directory; lookup then falls back to the interpreter's Scripts/bin dir and the verified cache (or a pinned fetch in
resolve()).status()(headroom tools doctor) uses the same rule so it no longer reports such a file ason-path.Type of Change
Changes Made
headroom/binaries.py: new_which_outside_cwd();_path_lookup()andstatus()use it. A warning names the ignored file.tests/test_binaries.py: realshutil.whichwithPATH=.and a tool in the cwd forwhich(),resolve()andstatus(), plus a test that a normal PATH hit outside the cwd is still used.Testing
pytest)ruff check .)mypy headroom)Test Output
Real Behavior Proof
HEADROOM_BINARIES_CACHE,HEADROOM_BINARIES_OFFLINE=1.difft, run withPATH=.:python -c 'from headroom import binaries as b; print(b.which("difft")); print([(r["state"], r["path"]) for r in b.status() if r["tool"]=="difft"])'.which: difft,status: [('on-path', './difft')]Ignoring difft found via the current directory (.../proj/difft); using the interpreter's Scripts dir or the Headroom cache instead,which: None,status: [('missing', None)]shutil.whichreturns a relative.\namehit there).Runtime Rollout Safety
Review Readiness
Checklist
CHANGELOG.mdAdditional Notes
No docs change needed. Fix adapted from a proposal by HetCreep (this version also ignores hits from relative PATH entries other than
.).🤖 Generated with Claude Code