fix(claude): detect the CLI version on Windows instead of pinning the stale fallback - #74
Open
w3a11y wants to merge 1 commit into
Open
fix(claude): detect the CLI version on Windows instead of pinning the stale fallback#74w3a11y wants to merge 1 commit into
w3a11y wants to merge 1 commit into
Conversation
… fallback npm installs Claude Code on Windows as `claude.cmd`; no `claude.exe` exists. `execFileSync` cannot spawn either name directly there — the extensionless one fails `ENOENT` because `execFileSync` does not apply `PATHEXT`, and the `.cmd` fails `EINVAL` because Node refuses to spawn batch files without a shell (the CVE-2024-27980 fix). `detectClaudeVersion()` therefore could never succeed on Windows. The bare `catch {}` swallowed both errors, so every request advertised `CLAUDE_CLI_FALLBACK_VERSION` regardless of the installed version. Anthropic gates models on that user-agent, so this made newer models unusable on Windows. Sonnet 4.5 requires 2.1.251+ and failed with `claude_code_version_too_old` naming 2.1.234 — the constant, not the installed CLI (2.1.263 locally). `claude update` could not help, because the installed version was never read. Route the Windows probes through `cmd.exe`, keeping the argument inside the command string so Node does not warn about unescaped shell arguments (DEP0190). Non-Windows behaviour is unchanged. Also raise the fallback to 2.1.263. It is sent verbatim whenever detection fails for any reason (CLI absent, sandboxed spawn), so at 2.1.234 the safety net sat below the floor Anthropic enforces and was itself a guaranteed HTTP 400. Both regression tests fail against the previous implementation and pass with this change. The existing empty-PATH fallback test still passes: cmd.exe honours an empty PATH for command lookup.
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.
Problem
npm installs Claude Code on Windows as
claude.cmd(a batch shim) — there is noclaude.exe.execFileSynccannot spawn either name directly:execFileSync('claude', …)ENOENT—execFileSyncdoes not applyPATHEXT, so the extensionless name resolves to nothingexecFileSync('claude.cmd', …)EINVAL— Node refuses to spawn.cmd/.batwithoutshell: true(the CVE-2024-27980 fix, Node >= 18.20.2/20.12.2/21.7.3)So
detectClaudeVersion()could never succeed on Windows. The barecatch {}swallowed both errors and every request advertisedCLAUDE_CLI_FALLBACK_VERSIONregardless of what was installed.Since Anthropic gates models on the version in that
user-agent, this made newer models unusable on Windows:The
2.1.234in that message is the hardcoded constant, not the installed version — I had 2.1.263.claude updatecannot fix it, because the installed version is never read.Fix
Route the Windows probes through
cmd.exe, withclaude.cmdas a second candidate. The argument sits inside the command string rather than in the argv array, which avoids Node'sDEP0190warning about unescaped shell arguments. Non-Windows behaviour is unchanged — same singleexecFileSync('claude', ['--version'])call.Three incidental hardenings, all Windows-motivated:
timeout3000 -> 10000.cmd.exestartup is now in the path and 3s is tight on a cold filesystem cache; a timeout here silently costs the real version.stdio: ['ignore', 'pipe', 'ignore']keeps CLI stderr chatter out of the parsed text.Fallback bump
CLAUDE_CLI_FALLBACK_VERSIONalso goes2.1.234->2.1.263.It is sent verbatim whenever detection fails for any reason — CLI not on
PATH, restricted spawn, sandbox — so at2.1.234the safety net had itself drifted below the floor Anthropic enforces, making it a guaranteed HTTP 400 on every platform rather than a graceful degradation.Tests
Two regression tests added to
test/detect-cli.spec.ts.The first cannot assume a real CLI in CI, so it writes a tiny stub
claude/claude.cmdto a temp dir, puts that dir onPATH, and asserts detection reports the stub's version rather than the constant — pinning the property that actually broke, on every platform.Verified they fail against the current implementation and pass with this change:
Full suite green (
pnpm test, which runstscfirst): 421/421.The existing
returns the fallback when claude is not in PATHtest still passes — I checked specifically, sinceshell: truecould plausibly have broken it:cmd.exehonours an emptyPATHfor command lookup, so the stubbed-out environment still yields the fallback.Verification
Confirmed on Windows 11 (build 26200), Node v24.0.2, npm 11.6.2, Claude Code 2.1.263 installed as
claude.cmd: detection now returns2.1.263in ~60 ms and Sonnet 4.5 works.One thing I did not change, but worth flagging: the bare
catch {}is what made this hard to diagnose. The resulting error names a version that appears nowhere in the user's install and suggests a remedy that cannot work. A single debug log on the fallback path would point straight at the cause. Happy to add that here or separately if you'd like.