claude-code - 💡(How to fix) Fix [BUG] MCP loader env-var validator misparses POSIX parameter expansion (e.g. ${var%pattern}) as missing env var

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Claude Code's MCP-config validator scans the args[] strings of stdio MCP servers for ${...} placeholders and treats anything inside the braces as an environment variable name. The matching regex appears to be greedy and accepts any character between ${ and }, so POSIX shell parameter-expansion forms like ${var%pattern} (suffix removal), ${var#prefix} (prefix removal), ${var/old/new} (substitution), and so on are misinterpreted as env-var references. The validator then refuses to start the server, complaining that no env var with that name exists.

Real environment-variable identifiers must match [A-Za-z_][A-Za-z0-9_]*. Names like _R%/ cannot exist — % and / are not legal in identifiers. So the validator rejecting them as "missing" is a parser bug, not a config error.

Error Message

Real environment-variable identifiers must match [A-Za-z_][A-Za-z0-9_]*. Names like _R%/ cannot exist — % and / are not legal in identifiers. So the validator rejecting them as "missing" is a parser bug, not a config error. If parameter expansion needs to be expressly forbidden (e.g. for sandbox reasons), the validator should produce a clearer error — something like "args[N] contains POSIX parameter expansion ${_R%/}; only ${VAR} and ${VAR:-default} are supported in MCP args" — instead of pretending an unrelated env var is missing.

Root Cause

Reports on the plugin tracker confirming the same root cause:

Fix Action

Fix / Workaround

  • thedotmack/claude-mem#2356 (most detailed, includes suggested upstream patch)
  • thedotmack/claude-mem#2354
  • thedotmack/claude-mem#2350

Workaround for plugin authors targeting Claude Code only

Code Example

{
  "mcpServers": {
    "demo": {
      "type": "stdio",
      "command": "sh",
      "args": ["-c", "_X=foo; echo \"${_X%o}\""]
    }
  }
}

---

... | while IFS= read -r _R; do _R="${_R%/}"; ...

---

\$\{([A-Za-z_][A-Za-z0-9_]*)(?::-[^}]*)?\}

---

{
  "mcpServers": {
    "my-server": {
      "type": "stdio",
      "command": "node",
      "args": ["${CLAUDE_PLUGIN_ROOT}/scripts/mcp-server.cjs"]
    }
  }
}
RAW_BUFFERClick to expand / collapse

Summary

Claude Code's MCP-config validator scans the args[] strings of stdio MCP servers for ${...} placeholders and treats anything inside the braces as an environment variable name. The matching regex appears to be greedy and accepts any character between ${ and }, so POSIX shell parameter-expansion forms like ${var%pattern} (suffix removal), ${var#prefix} (prefix removal), ${var/old/new} (substitution), and so on are misinterpreted as env-var references. The validator then refuses to start the server, complaining that no env var with that name exists.

Real environment-variable identifiers must match [A-Za-z_][A-Za-z0-9_]*. Names like _R%/ cannot exist — % and / are not legal in identifiers. So the validator rejecting them as "missing" is a parser bug, not a config error.

Minimal repro

A .mcp.json whose stdio server uses any POSIX parameter expansion in args[]:

{
  "mcpServers": {
    "demo": {
      "type": "stdio",
      "command": "sh",
      "args": ["-c", "_X=foo; echo \"${_X%o}\""]
    }
  }
}

Expected: server starts; sh evaluates ${_X%o} as suffix-strip, prints fo. Actual: validator refuses to start the server with Missing environment variables: _X%o.

In the wild

This is currently breaking the claude-mem plugin (claude-mem@thedotmack v12.7.5+), whose .mcp.json ships a shell-script auto-locator that uses ${_R%/} to strip a trailing slash from a path:

... | while IFS= read -r _R; do _R="${_R%/}"; ...

Symptom: Plugin (claude-mem @ claude-mem@thedotmack): MCP server mcp-search invalid: Missing environment variables: _R%/. This breaks every plugin tool that depends on the MCP server (memory search, knowledge graph queries, timeline, etc.).

Reports on the plugin tracker confirming the same root cause:

  • thedotmack/claude-mem#2356 (most detailed, includes suggested upstream patch)
  • thedotmack/claude-mem#2354
  • thedotmack/claude-mem#2350

The plugin author has a legitimate reason to use POSIX expansion: per thedotmack/claude-mem#2347, Claude Code substitutes ${CLAUDE_PLUGIN_ROOT} into args[] but does not export it as an env var, so a sh -c wrapper can't see it — they need an in-script auto-locator that finds the install dir from filesystem heuristics. POSIX parameter expansion is the natural tool for that.

Suggested fix

Tighten the env-var detection regex from "anything inside ${...}" to a proper env-var identifier with optional default-value suffix:

\$\{([A-Za-z_][A-Za-z0-9_]*)(?::-[^}]*)?\}

This still matches the documented forms Claude Code intends to support (${VAR}, ${VAR:-default}) and now correctly skips POSIX expansions like ${var%pattern}, ${var#prefix}, ${var/old/new}, ${#var}, etc. so they reach the spawned shell intact.

If parameter expansion needs to be expressly forbidden (e.g. for sandbox reasons), the validator should produce a clearer error — something like "args[N] contains POSIX parameter expansion ${_R%/}; only ${VAR} and ${VAR:-default} are supported in MCP args" — instead of pretending an unrelated env var is missing.

Environment

  • OS: Linux x86_64 (same root cause reported on macOS and Windows in the plugin issues above)
  • Claude Code version: latest stable as of 2026-05-08
  • MCP host: stdio via .mcp.json

Workaround for plugin authors targeting Claude Code only

Avoid sh -c wrappers in args[]; use command: "node" (or the language runtime directly) plus a direct file path with ${CLAUDE_PLUGIN_ROOT} substitution. Example:

{
  "mcpServers": {
    "my-server": {
      "type": "stdio",
      "command": "node",
      "args": ["${CLAUDE_PLUGIN_ROOT}/scripts/mcp-server.cjs"]
    }
  }
}

This skips the shell entirely so parameter expansion never enters the picture. The downside is loss of in-script logic for handling multi-host install layouts (Codex CLI, secondary CLAUDE_CONFIG_DIR, marketplace dev mode), which is the very capability claude-mem was trying to preserve.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING