VS Code extension: synchronous shell launch in resolveShellPath blocks event loop, causing extension host to be marked unresponsive

Open 💬 0 comments Opened Jun 12, 2026 by beningreenjam

Summary

The Claude Code VS Code extension (v2.1.175) makes a synchronous shell invocation during activation to resolve the user's PATH. If shell startup is slow (e.g. Oh My Zsh, brew shellenv, nvm, etc.), this blocks the Node.js event loop long enough for VS Code to mark the extension host as unresponsive — killing and restarting it in an infinite loop. The extension never successfully initialises.

Behaviour

Every session follows this pattern:

  1. Extension activates, AuthManager initialised, MCP server starts, OAuth tokens found
  2. Webview sends {"type":"init"} request
  3. No response is ever sent — the extension host is unresponsive
  4. VS Code kills the extension host (~90 seconds after marking it unresponsive) and restarts it
  5. Repeat indefinitely

Observed in VS Code renderer log every session:

Extension host (LocalProcess pid: XXXXX) is unresponsive.

Root Cause

resolveClaudeBinary() calls Gs.resolveShellPath() synchronously to build the PATH environment for child processes:

resolveClaudeBinary() {
  let t = kd(Gs.resolveShellPath(this.output))  // ← synchronous, blocks event loop
  // ...
}

resolveShellPath runs the user's login interactive shell to capture PATH:

static resolveShellPath(e) {
  let t = ce.env.shell  // e.g. /bin/zsh
  // ...
  let { stdout: n } = va(t, ["-lic", `printf '${i}'; command printenv PATH; printf '${i}'`], { timeout: 1e4 })
  // va is a synchronous exec (execa.sync / spawnSync)
}

The result is cached in Gs.shellPathCache per process, but the cache is lost on every extension host restart. Since becoming unresponsive triggers a restart, this blocks again each cycle.

Environment

  • OS: macOS 15.5 (Darwin 25.5.0, Apple Silicon)
  • VS Code: 1.124.0
  • Extension: anthropic.claude-code-2.1.175-darwin-arm64
  • Shell: /bin/zsh with Oh My Zsh — startup time ~1.5 s in terminal, likely longer in VS Code's minimal launch environment

Workaround

Adding a non-TTY short-circuit to ~/.zprofile and ~/.zshrc reduces the shell startup from ~1.5 s to ~27 ms for the extension's PATH probe, which keeps the block below VS Code's unresponsive threshold:

# ~/.zprofile — add before brew shellenv
[[ -t 1 ]] || { export PATH="/opt/homebrew/bin:/opt/homebrew/sbin:$HOME/.local/bin:/usr/local/bin:$PATH"; return; }

# ~/.zshrc — add before Oh My Zsh
[[ -t 1 ]] || return

This works because the extension pipes stdout (so [[ -t 1 ]] is false), whereas real terminal sessions have a TTY attached.

Suggested Fix

One or more of:

  1. Make resolveShellPath async — use child_process.spawn with a callback / Promise instead of a synchronous exec. The resolved PATH could be cached before the first init request is processed.
  2. Persist the shell PATH cache to disk — so it survives extension host restarts and the blocking launch only happens once ever, not once per restart cycle.
  3. Reduce the shell invocation flags — use -lc (login, non-interactive) instead of -lic (login, interactive) to avoid sourcing ~/.zshrc, which is the main source of latency for many users.

View original on GitHub ↗