fix(server): resolve Claude launcher shims from pnpm and space-containing Windows installs - #5678
Conversation
…ning installs on Windows Windows resolution for the Claude Agent SDK's pathToClaudeCodeExecutable only recognized npm's fixed node_modules/@anthropic-ai/claude-code layout next to a .cmd/.bat/.ps1 launcher shim. pnpm's global installs point the shim at a version-pinned pnpm store path instead (global/5/.pnpm/<pkg>@<version>/node_modules/<pkg>/...), which that fixed candidate list can never match, so a pnpm-installed claude-code silently fell back to the unspawnable shim path. Parse the shim's own %~dp0/$basedir-relative reference to its real target instead of guessing a fixed layout - this covers npm, pnpm, and any other tool built on the same cmd-shim convention (e.g. corepack) without hardcoding per-manager paths. The fixed npm candidate list stays as a fallback. Verified against real installs on this machine (npm global + a pnpm global install) and added regression tests, including install directories containing spaces. Adds a windows-latest CI job running the Claude/WSL-owning test files directly (they were previously only exercised with a mocked platform service on Linux/macOS runners), and documents native-vs-WSL Claude behavior on Windows in docs/user/providers-claude.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
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. Comment |
ApprovabilityVerdict: Needs human review This PR introduces new file-reading and regex-parsing logic to resolve Windows launcher shims, which is substantial new runtime behavior beyond a simple fix. While well-tested with preserved fallback, the parsing logic and unfamiliar author warrant human verification. You can customize Macroscope's approvability policy. Learn more. |
What was broken or under-supported
Claude was already a fully-built provider (
ClaudeAdapter,ClaudeExecutable, etc.), and native Windows launch support existed (#3740) for the common case: an npm global install (npm install -g @anthropic-ai/claude-code), where Node can't spawn the resulting.cmd/.bat/.ps1launcher shim directly (spawn EINVAL), soresolveClaudeSdkExecutablePathfollowed the shim to npm's fixednode_modules/@anthropic-ai/claude-code/{bin/claude.exe,cli.js}layout.That fixed-layout assumption breaks for other common Windows Node package managers. Verified on a real Windows machine: a pnpm global install's shim points at a version-pinned pnpm store path instead (
global/5/.pnpm/<pkg>@<version>/node_modules/<pkg>/...), which the npm-shaped candidate list can never match — the resolver silently fell back to the unspawnable shim path itself, and session start would fail.Also verified: Windows CI never actually ran this code.
.github/workflows/ci.ymlonly tests on Ubuntu/macOS runners; the Windows-specific logic was exercised solely with a mocked platform service.What changed
apps/server/src/provider/Drivers/ClaudeExecutable.ts: instead of guessing a fixed relative layout, parse the shim script's own self-reference. Windows launcher shims (npm, pnpm, and anything else built on the samecmd-shimconvention, e.g. corepack) resolve their own directory (%~dp0/%dp0%in.cmd/.bat,$basedirin.ps1) and invoke the real entry relative to it — parsing that reference works regardless of which package manager generated the shim or how deep/versioned its target path is. The original fixed npm candidate list is kept as a fallback in case a shim format doesn't match the parser.Native Windows behavior
clauderesolves via PATH/PATHEXT (or an explicit Binary path setting) as before..cmd/.bat/.ps1shim, T3 Code now reads the shim's source and follows its own relative reference to the real executable/script, rather than assuming npm's specificnode_moduleslayout.C:\Users\Jane Doe\...) — verified with a regression test; nothing in the resolution path goes through shell parsing, so this was already safe once the right path is found.WSL fallback behavior
Unchanged and unaffected — T3 Code Desktop's existing dual-mode WSL backend (
DesktopWslBackend) runs the whole server inside WSL when enabled, at which point Claude Code runs like it does on Linux with no Windows-specific resolution involved. Documented this alongside native behavior indocs/user/providers-claude.mdsince it wasn't written down anywhere before.macOS verification
No code path here executes on macOS (
resolveClaudeSdkExecutablePathearly-returns the input unchanged for any non-win32platform), so this change can't affect macOS behavior. Reviewed the macOS desktop build/signing config for anything that could interfere with Claude Code's subprocess (Keychain access, sandboxing): nocom.apple.security.app-sandboxentitlement is applied, and the only entitlements added (allow-jit,allow-unsigned-executable-memory,disable-library-validation) are permissive, not restrictive, toward spawned child processes. Attempted to run the test suite on a physical Mac for extra confidence; the machine was unreachable (see Remaining limitations).Windows CI coverage
Added a
windows_claude_provider_testsjob (windows-latest) running the Claude- and WSL-owning test files directly:ClaudeExecutable.test.ts,ClaudeAdapter.test.ts,ProviderAdapterRegistry.test.ts, and the threeapps/desktop/src/wsltest files. Scoped deliberately, not the full suite — see limitations below.Tests/builds run
All run locally on real Windows (not just CI-mocked):
ClaudeExecutable.test.ts: 15/15 passing (8 pre-existing + 7 new: pnpm.cmd, pnpm.ps1, node.exe-self-reference skip, garbage-shim fallback, npm-shim-content parsing, spaced npm path, spaced pnpm path)ClaudeAdapter.test.ts,ProviderAdapterRegistry.test.ts,apps/desktop/src/wsl/*): 137/137 passingtsgo --noEmit) fort3and@t3tools/desktop: clean (only pre-existingsuggestion-level oxlint hints in untouched files)vp lint) on touched files: cleanvp fmt --check): cleanvp pack): succeedsclaude-codeinstall (resolves correctly, as before) and a real pnpm global install of an equivalent-shaped package (previously unresolvable, now resolves correctly to the real.pnpmstore target)Remaining limitations
claude auth login+ a full desktop session) wasn't performed — the physical Mac used for this kind of check was unreachable during this work. Not expected to matter given this PR's code never executes on macOS, but flagging it as unverified rather than claiming it.CodexHomeLayout.test.tsand the rest ofapps/server/src/provider/Drivers: those hit an unrelated, pre-existingEPERM: symlink not permittedfailure on Windows (symlink creation needs elevated privileges/Developer Mode, which hostedwindows-latestrunners don't have by default). That's a Codex-specific gap, out of scope here.cmd-shimconvention (npm, pnpm, corepack). A package manager using a fundamentally different shim mechanism could still fall through to the existing warn-and-return-original-path behavior.Note
Medium Risk
Changes only run on Windows when resolving Claude binaries; incorrect shim parsing could break session start for some install layouts, but behavior is covered by new tests and falls back to the previous npm layout.
Overview
Fixes native Windows Claude sessions failing when
clauderesolves to a global pnpm (or other cmd-shim) launcher: resolution no longer assumes npm’s fixednode_moduleslayout next to the shim.resolveClaudeSdkExecutablePathnow reads.cmd/.bat/.ps1shim source, parses%~dp0/$basedirrelative targets, skipsnode.exeself-references, and follows the first existing real entry—then keeps the prior npm candidate list as fallback. Tests cover pnpm.cmd/.ps1, garbage shims, and paths under directories with spaces.CI adds a
windows-latestjob scoped to Claude executable/adapter and desktop WSL tests so Windows-specific logic runs on a real runner. User docs add a Native vs WSL section describing shim-following and when to set an explicit binary path.Reviewed by Cursor Bugbot for commit a880430. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Fix Claude executable resolution for pnpm shims and Windows paths with spaces
resolveClaudeSdkExecutablePathnow reads.cmd/.bat/.ps1launcher shims and parses relative targets to locate the real Claude executable (e.g.,claude.exeorcli.js), skippingnode.exereferences.windows-latestto cover these paths.Macroscope summarized a880430.