Skip to content

fix(hooks): MCP health-check blocks all MCP calls on Windows — spawn npx ENOENT #1455

@mike-batrakov

Description

@mike-batrakov

Summary

mcp-health-check.js PreToolUse hook fails on Windows with spawn npx ENOENT, marking MCP servers as unhealthy and blocking all subsequent MCP tool calls until the cooldown expires.

This is the same class of Windows spawn bug as #1435 (GateGuard), but in a different hook.

Environment

  • OS: Windows 10 Pro (10.0.19045)
  • Shell: Git Bash (via Claude Code)
  • ECC version: 1.10.0
  • Claude Code: VS Code extension
  • Node/npx: Available on PATH (which npx/c/Program Files/nodejs/npx)

Steps to Reproduce

  1. Install ECC plugin with an MCP server configured in ~/.claude.json (e.g., npx -y @azure-devops/mcp biztechonline)
  2. Use any mcp__* tool in Claude Code on Windows
  3. The pre:mcp-health-check hook fires and attempts spawn('npx', ...)
  4. Spawn fails with ENOENT because Node.js child_process.spawn does not resolve .cmd extensions on Windows

Expected Behavior

The health check should either:

  • Resolve npx correctly on Windows (use npx.cmd, full path, or shell: true)
  • Gracefully skip the probe if the spawn fails, rather than marking the server unhealthy

Actual Behavior

[MCPHealthCheck] azure-devops is unavailable (spawn npx ENOENT). Blocking wit_my_work_items so Claude can fall back to non-MCP tools.
  • Server marked unhealthy with 30s backoff in ~/.claude/mcp-health-cache.json
  • All MCP tool calls blocked until cooldown expires
  • Manual edit of mcp-health-cache.json required to restore functionality
  • Cycle repeats on next health check probe

Workaround

Disable the hook via ECC_DISABLED_HOOKS in ~/.claude/settings.json:

{
  "env": {
    "ECC_DISABLED_HOOKS": "pre:mcp-health-check"
  }
}

Root Cause

In scripts/hooks/mcp-health-check.js, the hook uses spawn('npx', ...). On Windows, Node.js child_process.spawn does not automatically resolve .cmd extensions — npx is actually npx.cmd on Windows.

Suggested Fix

Same pattern as the GateGuard Windows fix in #1439:

// Option A: shell: true
spawn('npx', args, { ...opts, shell: true });

// Option B: platform-aware binary
const npxBin = process.platform === 'win32' ? 'npx.cmd' : 'npx';
spawn(npxBin, args, { ...opts });

Also consider: if the spawn probe fails, treat it as "unknown" rather than "unhealthy" — the MCP server itself may be perfectly reachable via Claude Code's own stdio transport.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions