VS Code extension: `resolveShellPath` uses `spawnSync` and blocks the extension host for ~1s at startup

Status Open
Reported on v2.1.300
Maintainer reply None cached
Activity 0 comments · opened Oct 15, 2026

Follow-up to #67948, which was auto-closed as stale and locked. The issue is still present in 2.1.300.

Summary

On startup, the Claude Code VS Code extension resolves the user's shell PATH by running the login shell synchronously (spawnSync). While the shell runs, the entire extension host is blocked, so every other extension's activation is delayed. With a typical oh-my-zsh + nvm setup this takes ~0.9–1.0s per window. With several restored windows, each one pays the cost.

Environment

  • Extension: anthropic.claude-code 2.1.300 (darwin-arm64)
  • VS Code: 1.142.0 (6d62b1d5dace93a33b3a6d3eca1317bd5e15bcb1), arm64
  • OS: macOS (Darwin 27.0.0), Apple M4 Max
  • Shell: zsh + oh-my-zsh, nvm plugin with autoload yes
  • claude binary installed via Homebrew (/opt/homebrew/bin/claude)

Evidence

CPU profile of the extension host (code --prof-startup, small workspace). From ~1.13s to ~2.04s after extension host start, the extension host is at 100% "busy" with a single stack:

spawnSync              node:internal/child_process
spawnSync              node:child_process
nF                     extension.js
zX                     extension.js
resolveShellPath       extension.js
resolveClaudeBinary    extension.js
getClaudeBinary        extension.js
refreshCliAuthStatus   extension.js
getAuthStatus          extension.js
processRequest         extension.js

Measured block durations:

| Shell config | zsh -lic time (clean env) | Block in profile |
|---|---|---|
| oh-my-zsh + nvm (loaded twice) | 0.86s | ~1000ms |
| oh-my-zsh + nvm (loaded once) | ~0.48s warm / ~0.85s cold | ~910ms |

The command run (from resolveShellPath):

zX(shell, ["-lic", `printf '${X}'; command printenv PATH; printf '${X}'`], { timeout: 1e4 })

zX → nF → child_process.spawnSync. The result is cached in shellPathCache, but only in memory, so every window or startup pays the cost again.

The startup performance report (Developer: Startup Performance) shows the same gap: no other extension loads or activates during this time.

Steps to reproduce

  1. Use a shell whose interactive login startup takes noticeable time (e.g. oh-my-zsh with the nvm plugin; time zsh -lic exit ≈ 0.5–1s).
  2. Quit VS Code and start it with code --prof-startup <folder>.
  3. Open the extension host .cpuprofile and look at ~1–2s: a long spawnSync from resolveShellPath.

Expected

Resolving the CLI path should not block the shared extension host. Other extensions should activate while the shell runs.

Suggested fixes (any one would help)

  1. Use async execFile/spawn and start resolution early in activate() as a promise; await it where the binary is needed.
  2. Check known install locations first (/opt/homebrew/bin/claude, /usr/local/bin/claude, ~/.local/bin/claude, ~/.claude/local/claude) and fall back to the shell only if none exist.
  3. Use the PATH already present in the extension host. VS Code resolves the shell environment itself at startup when launched from the Dock or Finder.
  4. Persist the resolved PATH in globalState (keyed by shell) and refresh it asynchronously in the background.
  5. Lower the 10s timeout to bound the worst case.

Impact

  • Delays every extension's activation by the user's shell startup time (here ~1s), in every window.
  • Shows up as "extension host unresponsive" when combined with other startup load.
  • Users with heavier shell configs (nvm, conda, pyenv, asdf) are affected most.

View original on GitHub ↗