Skip to content

fix(server): resolve Claude launcher shims from pnpm and space-containing Windows installs - #5678

Closed
MONKE2525E wants to merge 1 commit into
pingdotgg:mainfrom
MONKE2525E:feat/windows-claude-launch-hardening
Closed

fix(server): resolve Claude launcher shims from pnpm and space-containing Windows installs#5678
MONKE2525E wants to merge 1 commit into
pingdotgg:mainfrom
MONKE2525E:feat/windows-claude-launch-hardening

Conversation

@MONKE2525E

@MONKE2525E MONKE2525E commented Aug 8, 2026

Copy link
Copy Markdown

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/.ps1 launcher shim directly (spawn EINVAL), so resolveClaudeSdkExecutablePath followed the shim to npm's fixed node_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.yml only 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 same cmd-shim convention, e.g. corepack) resolve their own directory (%~dp0/%dp0% in .cmd/.bat, $basedir in .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

  • claude resolves via PATH/PATHEXT (or an explicit Binary path setting) as before.
  • If that lands on a .cmd/.bat/.ps1 shim, T3 Code now reads the shim's source and follows its own relative reference to the real executable/script, rather than assuming npm's specific node_modules layout.
  • Falls back to the previous fixed-candidate check, then to the shim path itself (existing warning-and-continue behavior) if nothing resolves.
  • Handles install directories containing spaces (e.g. 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 in docs/user/providers-claude.md since it wasn't written down anywhere before.

macOS verification

No code path here executes on macOS (resolveClaudeSdkExecutablePath early-returns the input unchanged for any non-win32 platform), 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): no com.apple.security.app-sandbox entitlement 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_tests job (windows-latest) running the Claude- and WSL-owning test files directly: ClaudeExecutable.test.ts, ClaudeAdapter.test.ts, ProviderAdapterRegistry.test.ts, and the three apps/desktop/src/wsl test 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)
  • Full Claude/WSL suite (ClaudeAdapter.test.ts, ProviderAdapterRegistry.test.ts, apps/desktop/src/wsl/*): 137/137 passing
  • Typecheck (tsgo --noEmit) for t3 and @t3tools/desktop: clean (only pre-existing suggestion-level oxlint hints in untouched files)
  • Lint (vp lint) on touched files: clean
  • Format check (vp fmt --check): clean
  • Server bundle build (vp pack): succeeds
  • Manually verified end-to-end against two real installs on this machine before writing the fix: a real npm global claude-code install (resolves correctly, as before) and a real pnpm global install of an equivalent-shaped package (previously unresolvable, now resolves correctly to the real .pnpm store target)

Remaining limitations

  • Live macOS runtime verification (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.
  • The new Windows CI job intentionally excludes CodexHomeLayout.test.ts and the rest of apps/server/src/provider/Drivers: those hit an unrelated, pre-existing EPERM: symlink not permitted failure on Windows (symlink creation needs elevated privileges/Developer Mode, which hosted windows-latest runners don't have by default). That's a Codex-specific gap, out of scope here.
  • Shim parsing covers the cmd-shim convention (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 claude resolves to a global pnpm (or other cmd-shim) launcher: resolution no longer assumes npm’s fixed node_modules layout next to the shim.

resolveClaudeSdkExecutablePath now reads .cmd/.bat/.ps1 shim source, parses %~dp0 / $basedir relative targets, skips node.exe self-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-latest job 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

  • On Windows, resolveClaudeSdkExecutablePath now reads .cmd/.bat/.ps1 launcher shims and parses relative targets to locate the real Claude executable (e.g., claude.exe or cli.js), skipping node.exe references.
  • Adds support for pnpm shim format in addition to the existing npm layout, and handles install paths containing spaces.
  • Falls back to the previous fixed npm package entry candidates if shim parsing yields no valid result.
  • Adds a new CI job running Claude/WSL-specific tests on windows-latest to cover these paths.
  • Adds a providers-claude.md section documenting Windows native vs. WSL resolution behavior.

Macroscope summarized a880430.

…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>
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 45427160-879d-425f-bfc6-0c532747900c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Aug 8, 2026
@macroscopeapp

macroscopeapp Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: 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.

@MONKE2525E
MONKE2525E marked this pull request as draft August 8, 2026 02:32
@MONKE2525E MONKE2525E closed this Aug 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L 100-499 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant