[BUG] 'env' vars from .claude/settings.json not passed to the plugins/mcps.

Status Open
Maintainer reply None cached
Activity 17 comments · opened Nov 19, 2025

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

I have developed a set of Claude code plugins for the company I work for. Part of using the plugins involves adding a settings.json file in the .claude folder within the local repository.
This is how it looks (of course, with secrets and information masked).

{
  "env": {
    "SET_OF_ENV_VARS": "ENV_VARS_VALUE",
  },
  "enabledPlugins": {
    "plugin_name@plugin_owner": true,
    "plugin_name@plugin_owner": true,
    "plugin_name@splugin_owner": true
  },
  "extraKnownMarketplaces": {
    "claude-code-plugins": {
      "source": {
        "source": "github",
        "repo": "NAME_OF_PLUGIN_REPO",
      }
    }
  }
}

The issue is that from a few versions onwards (I am not sure which one), you no longer pass the 'env' variables properly. As a result, when using claude --debug, it reports that some ENV vars are missing, even though I have them and it was working perfectly before. Consequently, many MCPs no longer function correctly, or due to their retry mechanisms, it results in duplicated sessions for that MCP (e.g., context7, Serena, etc).

As a note: if I put the env vars inside .env file and I use the MCPs locally directly (not through the plugin or passing .env vars through .claude/settings.json) - it seems to be working.

Let me know if you need more info.
Thanks

What Should Happen?

ENV vars should be passed

Error Messages/Logs

2025-11-18T07:02:43.389Z [DEBUG] Found 5 plugins (4 enabled, 1 disabled)
2025-11-18T07:02:43.389Z [DEBUG] Plugin not available for MCP: plugin_name@plugin_owner - error type: hook-load-failed
2025-11-18T07:02:43.390Z [WARN] Missing environment variables in plugin MCP config: OPENAI_API_KEY, VECTOR_STORE_HOST, VECTOR_STORE_USERNAME, VECTOR_STORE_PASSWORD
2025-11-18T07:02:43.390Z [WARN] Missing environment variables in plugin MCP config: SERENA_PROJECT_PATH

Steps to Reproduce

  1. Create a plugin that requires environment variables to be passed, for MCPs or other purposes.
  2. Set it up in a local repository with .claude/settings.json, enabling the plugin and providing the necessary .env variables.
  3. Attempt to use it and verify that everything functions correctly.

Claude Model

None

Is this a regression?

Yes, this worked in a previous version

Last Working Version

_No response_

Claude Code Version

2.0.46 (Claude Code)

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

VS Code integrated terminal

Additional Information

_No response_

View original on GitHub ↗

15 Comments

b0dea · 9 months ago

Any update on this one?

matscederholm · 8 months ago

OTLP Telemetry Not Working Due to Env Var Bug

Version: Claude Code 2.0.65
Platform: macOS
Impact: Critical - All OTLP telemetry stopped working

Issue

Environment variables from ~/.claude/settings.json are not being loaded by Claude Code, which breaks OTLP telemetry completely. The env section configuration is correct and validated:

{
  "env": {
    "CLAUDE_CODE_ENABLE_TELEMETRY": "1",
    "OTEL_METRICS_EXPORTER": "otlp",
    "OTEL_EXPORTER_OTLP_ENDPOINT": "http://10.90.55.3:4317",
    "OTEL_EXPORTER_OTLP_PROTOCOL": "grpc",
    "OTEL_METRIC_EXPORT_INTERVAL": "10000",
    "OTEL_RESOURCE_ATTRIBUTES": "user.email=user@example.com"
  }
}

Verification

Running ps eww -p <claude-pid> confirms NO OTEL environment variables are set for the Claude Code process, despite being correctly configured in settings.json.

Running lsof -nP -i4TCP | grep claude shows Claude Code has NO active connections to the configured OTLP endpoint (port 4317), confirming the env vars aren't being loaded.

Impact

  • Datadog OTLP ingestion receives zero metrics
  • Affects entire team that upgraded to 2.0.65
  • Monitoring dashboards completely broken
  • Worked in previous versions, confirmed regression

This significantly impacts teams using OTLP for monitoring Claude Code usage and costs.

b0dea · 7 months ago

Has this been fixed?

eduardolat · 7 months ago

+1

nagromLobo · 7 months ago

+1

tylerstraub · 7 months ago

+1

mattmcde78 · 7 months ago

+1

jat255 · 7 months ago

I was seeing this, and it turns out it was because I had IS_DEMO set in my environment. Removing that fixed the issue

edumusikse · 6 months ago

Adding diagnostic evidence from a separate reproduction (originally filed as #22571, closing as duplicate).

Reproduction context: .mcp.json in project root with @modelcontextprotocol/server-memory:

{
  "mcpServers": {
    "memory": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-memory"],
      "env": {
        "MEMORY_FILE_PATH": "/Users/me/.claude-memory/memory.json"
      }
    }
  }
}

Confirmed via process inspection:

  • Claude Desktop's spawned server (via claude_desktop_config.json): MEMORY_FILE_PATH is set ✅
  • Claude Code's spawned server (via .mcp.json): MEMORY_FILE_PATH is not set

Both use identical config — only the Claude Code spawner fails to pass env vars through.

Impact for server-memory specifically: Knowledge graph data is not persisted to the configured file path and is not shared with Claude Desktop, since the server silently falls back to an ephemeral in-memory store with no warning.

Environment: macOS Darwin 25.2.0 (arm64), Claude Code latest as of 2026-02-02.

prong-raft-squishy · 6 months ago

Workaround for anyone hitting this: Use a wrapper script that sets the env vars before exec, then point your MCP config at the wrapper instead of the bare command.

Example for Playwright MCP (needed PLAYWRIGHT_MCP_BROWSER=chromium):

#!/bin/bash
export PLAYWRIGHT_MCP_BROWSER=chromium
exec npx @playwright/mcp@latest "$@"

In ~/.claude/.mcp.json:

{
  "mcpServers": {
    "playwright": {
      "command": "/path/to/playwright-mcp-wrapper",
      "args": []
    }
  }
}

The wrapper sets the env in its own process before exec, so it doesn't depend on Claude Code passing it through. Works for any MCP server that needs env vars.

Full details: https://gist.github.com/prong-raft-squishy/9b1ff44310c90de9fc70c0232d67632f

kws1207 · 6 months ago

This bug has been open since November 2025 — over 3 months with bug, has repro, and area:mcp labels, yet no assignee or milestone.

Multiple users have confirmed the issue with detailed reproduction steps (#22571, #28090 closed as duplicates, #23216 still open separately). The community-suggested wrapper script workaround also doesn't work in the VSCode extension — the extension doesn't even pick up the updated mcp.json, continuing to spawn the server with the old config.

This is a basic contract: env block in MCP config should be passed to the spawned process. It worked before, it broke, and it hasn't been fixed.

Can someone from the team please prioritize this? cc @bcherny @ant-kurt @fvolcic

bytemines · 5 months ago

This might help partially. I built a config orchestrator that has an env template system — .env.template gets tracked in git with the variable names, .env.local stays gitignored with the actual values. When you set up on a new machine, mycelium env setup walks you through filling in the values.

Not a full fix for the underlying issue but it makes managing MCP env vars across machines less painful.

https://github.com/bytemines/mycelium

Valentyn-Kubrak · 4 months ago

Can confirm this is still an issue. In our case, env variables in plugin .mcp.json with stdio transport load inconsistently — sometimes they resolve correctly, sometimes they don't, with no changes to the config between sessions. This makes plugin-distributed MCP servers unreliable and hard to debug.

Would really appreciate a fix for this.

Valentyn-Kubrak · 4 months ago

As a temporary workaround, I wrote a script that merges plugin .mcp.json configs into the project-level .mcp.json with env vars interpolated from .claude/settings.local.json. Maybe it helps someone:


#!/usr/bin/env bash
# build-mcp.sh — Workaround for Claude Code bug #11927
# Merges plugin .mcp.json configs into project-level .mcp.json
# with env vars interpolated from .claude/settings.local.json
#
# Usage: ./scripts/build-mcp.sh
#
# What it does:
#   1. Reads env vars from .claude/settings.local.json
#   2. Finds .mcp.json from each enabled plugin (latest version in cache)
#   3. Interpolates ${VAR} patterns with actual values
#   4. Merges everything into project .mcp.json (gitignored)
#   5. Preserves any non-plugin servers already in .mcp.json

set -euo pipefail

PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
SETTINGS_LOCAL="$PROJECT_DIR/.claude/settings.local.json"
SETTINGS_PROJECT="$PROJECT_DIR/.claude/settings.json"
MCP_OUT="$PROJECT_DIR/.mcp.json"
PLUGIN_CACHE="$HOME/.claude/plugins/cache"

# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
NC='\033[0m'

if ! command -v jq &>/dev/null; then
  echo -e "${RED}Error: jq is required. Install with: brew install jq${NC}"
  exit 1
fi

if [[ ! -f "$SETTINGS_LOCAL" ]]; then
  echo -e "${RED}Error: $SETTINGS_LOCAL not found. Run /setup-tokens first.${NC}"
  exit 1
fi

if [[ ! -f "$SETTINGS_PROJECT" ]]; then
  echo -e "${RED}Error: $SETTINGS_PROJECT not found.${NC}"
  exit 1
fi

echo "Build MCP"
echo "Settings: $SETTINGS_LOCAL"
echo ""

# Load env vars as JSON object for jq-based interpolation
ENV_JSON=$(jq -c '.env // {}' "$SETTINGS_LOCAL")
ENV_COUNT=$(echo "$ENV_JSON" | jq 'length')
echo -e "  Loaded ${GREEN}${ENV_COUNT}${NC} env vars from settings.local.json"

# Get enabled plugins
ENABLED_PLUGINS=$(jq -r '.enabledPlugins // {} | to_entries[] | select(.value == true) | .key' "$SETTINGS_PROJECT")

# Start: preserve manual (non-plugin) servers from existing .mcp.json
if [[ -f "$MCP_OUT" ]]; then
  MANUAL_SERVERS=$(jq '.mcpServers // {} | with_entries(select(.value.__plugin_source == null))' "$MCP_OUT")
  MANUAL_COUNT=$(echo "$MANUAL_SERVERS" | jq 'length')
  if [[ "$MANUAL_COUNT" -gt 0 ]]; then
    echo -e "  Preserved ${MANUAL_COUNT} manual MCP server(s)"
  fi
else
  MANUAL_SERVERS='{}'
fi

MERGED="$MANUAL_SERVERS"
PLUGIN_COUNT=0
SERVER_COUNT=0

for plugin_entry in $ENABLED_PLUGINS; do
  plugin_name="${plugin_entry%%@*}"
  marketplace="${plugin_entry#*@}"

  plugin_cache_dir="$PLUGIN_CACHE/$marketplace/$plugin_name"
  [[ -d "$plugin_cache_dir" ]] || continue

  latest_version=$(ls -d "$plugin_cache_dir"/*/ 2>/dev/null | sort -V | tail -1)
  [[ -n "$latest_version" ]] || continue

  mcp_file="${latest_version}.mcp.json"
  [[ -f "$mcp_file" ]] || continue

  plugin_servers=$(jq -c '.mcpServers // {}' "$mcp_file")
  [[ "$plugin_servers" != "{}" ]] || continue

  # Interpolate ${VAR} and ${VAR:-default} using jq
  # Also add __plugin_source marker
  interpolated=$(echo "$plugin_servers" | jq --argjson env "$ENV_JSON" --arg src "$plugin_name" '
    # Walk all string values and replace ${VAR} patterns
    def interpolate:
      if type == "string" then
        # Replace ${VAR:-default} and ${VAR} patterns
        reduce ($env | to_entries[]) as $e (.;
          gsub("\\$\\{" + $e.key + ":-[^}]*\\}"; $e.value) |
          gsub("\\$\\{" + $e.key + "\\}"; $e.value)
        )
      elif type == "array" then
        map(interpolate)
      elif type == "object" then
        with_entries(.value |= interpolate)
      else
        .
      end;
    with_entries(
      .value |= (interpolate | . + {"__plugin_source": $src})
    )
  ')

  server_count=$(echo "$interpolated" | jq 'length')
  SERVER_COUNT=$((SERVER_COUNT + server_count))
  PLUGIN_COUNT=$((PLUGIN_COUNT + 1))

  # Merge
  MERGED=$(echo "$MERGED" | jq --argjson new "$interpolated" '. + $new')

  version_name=$(basename "$latest_version")
  server_names=$(echo "$interpolated" | jq -r 'keys[]' | tr '\n' ', ' | sed 's/,$//')
  echo -e "  ${GREEN}✓${NC} $plugin_name ($version_name): $server_names"
done

# Check for unresolved ${VAR} patterns
unresolved=$(echo "$MERGED" | grep -oE '\$\{[A-Za-z_][A-Za-z0-9_]*(:-[^}]*)?\}' | sort -u || true)
if [[ -n "$unresolved" ]]; then
  echo ""
  echo -e "  ${YELLOW}Warning: unresolved variables:${NC}"
  for var in $unresolved; do
    echo -e "    ${YELLOW}$var${NC}"
  done
fi

# Write
jq -n --argjson servers "$MERGED" '{"mcpServers": $servers}' > "$MCP_OUT"

echo ""
echo -e "${GREEN}Done!${NC} $MCP_OUT updated"
echo "  Plugins: $PLUGIN_COUNT"
echo "  MCP servers: $SERVER_COUNT"
echo ""
echo "Restart Claude Code session to pick up changes."
Blackspell01 · 3 months ago

how is it possible that ts is not fixed yet?

Showing cached comments. Read the full discussion on GitHub ↗