fix: codepage-safe agent binary resolution on non-English Windows#528
Merged
Conversation
On a non-English Windows (e.g. GBK/936 console codepage), execSync decoded `where`/`which` stdout with the wrong codepage, mangling a non-ASCII (Chinese) username in the path into garbage like `C:\Users\??.?[\AppData\Roaming\npm\claude.cmd` and producing an ENOENT when the agent tried to run. Adapters also returned the unverified hit directly, and codex/gemini returned an empty string on a miss that short-circuited the Node-derived fallback tiers. - paths.js: add whereBinary() (chcp 65001 forces UTF-8 `where` output + fs.existsSync guard) and harden whichBinary() the same way. - Route every CLI adapter (claude/cline/codex/copilot/cursor/hermes/opencode/ gemini, plus claude's openagents MCP lookup) through whereBinary so a mangled path is rejected and resolution falls through to Node-derived (APPDATA/ homedir, correctly-encoded) tiers. - Bump to 0.2.144. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
The previous commit prefixed the Windows `where` lookup with `chcp 65001` to force UTF-8 output. Under `windowsHide: true` cmd runs console-less (CREATE_NO_WINDOW), where `chcp` can fail with an invalid-handle error — and because it was chained with `&&`, that failure would make EVERY binary lookup return null on Windows (no agent resolvable). The CI windows matrix cells caught this. Replace it with a strictly-safer scheme that needs no codepage juggling: - whereBinary: check `%APPDATA%\npm\<name>.cmd` FIRST (APPDATA is UTF-16 from the OS via Node — always correctly encoded, and where npm-global CLIs live), then fall back to an existence-guarded `where`/`which`. - whichBinary: keep the existsSync guard, drop the chcp prefix. A mangled non-ASCII path still fails existsSync and the caller falls through to its Node-derived tiers, so the original `C:\Users\<中文>\…\claude.cmd` ENOENT stays fixed without risking the common case. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zomux
approved these changes
Jun 29, 2026
zomux
left a comment
Contributor
There was a problem hiding this comment.
Sound codepage fix — checks %APPDATA%\npm shim first (UTF-16-safe) + existence-guards every 'where' hit so a mangled path fails safe. Spawn safety preserved (only the lookup tier centralized; adapters keep their own .cmd/cmd.exe handling). Also fixes a latent codex/gemini enriched-env bug. Nit: add a unit test for whereBinary. LGTM.
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.
On a non-English Windows (e.g. GBK/936 console codepage), execSync decoded
where/whichstdout with the wrong codepage, mangling a non-ASCII (Chinese) username in the path into garbage likeC:\Users\??.?[\AppData\Roaming\npm\claude.cmdand producing an ENOENT when the agent tried to run. Adapters also returned the unverified hit directly, and codex/gemini returned an empty string on a miss that short-circuited the Node-derived fallback tiers.whereoutput + fs.existsSync guard) and harden whichBinary() the same way.