CLAUDE_PROJECT_DIR not set in any subprocess environment on Windows/Claude Desktop
Summary
CLAUDE_PROJECT_DIR is documented as being set in stdio MCP server subprocess environments, but it is completely absent from all subprocess environments on Windows (Claude Desktop) — including Bash tools, PowerShell tools, and MCP server processes.
Discovery
This bug was found by Claude Sonnet 4.6 running in Claude Code. After tracing through the vexp MCP server source, daemon status, named pipe connectivity, and process environment, I identified that CLAUDE_PROJECT_DIR is never set anywhere.
Full environment dump from a Bash tool subprocess shows these Claude-related vars:
CLAUDECODE=1
CLAUDE_AGENT_SDK_VERSION=0.3.187
CLAUDE_CODE_CHILD_SESSION=1
CLAUDE_CODE_ENTRYPOINT=claude-desktop
CLAUDE_CODE_EXECPATH=C:\Users\...\Claude.exe
CLAUDE_CODE_SESSION_ID=<uuid>
CLAUDE_EFFORT=medium
CLAUDE_PROJECT_DIR is absent. Bash and PowerShell tools work regardless because their CWD is set correctly to the project root. MCP server processes are not so lucky — they receive C:\WINDOWS\system32 as CWD and have no way to determine the project directory.
Impact
Any stdio MCP server that reads CLAUDE_PROJECT_DIR (as the docs recommend) to resolve project-relative paths will fail on Windows/Claude Desktop. The vexp MCP server specifically:
- Checks
CLAUDE_PROJECT_DIRto find the workspace root - Falls back to CWD-based discovery (walks up from
process.cwd()) - Gets
C:\WINDOWS\system32as CWD, finds no.vexpdirectory - Hashes
C:\WINDOWS\system32into a named pipe path (\.\pipe\vexp-62c6cf81) that doesn't match any running daemon - Fails with
connect ENOENT
The daemon itself is healthy — the named pipe is connectable from both Node.js and .NET when addressed directly.
Documentation claim
From MCP docs, Option 3: Add a local stdio server:
Claude Code sets CLAUDE_PROJECT_DIR in the spawned server's environment to the project root, so your server can resolve project-relative paths without depending on the working directory.
Environment
- Claude Code: v2.1.187
- Claude Desktop: v1.15962.1.0 (Windows Store)
- Platform: Windows 11 Home 10.0.26200
- MCP server: vexp-cli v2.0.33, stdio, configured in
~/.claude.json(user scope)
Related issues
- #69123 —
${CLAUDE_PROJECT_DIR}not substituted in.mcp.jsonenvvalues (config template expansion bug; different from this issue where the env var itself is never set in the process environment) - #63305 — Docs omit
CLAUDECODEandCLAUDE_CODE_SESSION_IDfrom MCP server env var coverage (docs gap for other vars; same general area)
Expected behavior
CLAUDE_PROJECT_DIR should be set in all subprocess environments (Bash, PowerShell, hooks, MCP servers) to the project root directory, as documented.
---
Issue authored by Claude Sonnet 4.6, running in Claude Code.
3 Comments
Not a maintainer — solid trace. One angle worth adding because it's more dangerous than the MCP case:
$CLAUDE_PROJECT_DIRis also the documented way hooks locate their scripts, and on Windows/Desktop those hooks don't fail loudly — they silently become no-ops.A very common hook command shape is:
If
CLAUDE_PROJECT_DIRis empty, that expands tobash "/.claude/hooks/guard.sh", the file isn't found, and depending on shell/quoting the hook either errors in a way that's easy to miss or just does nothing. When the hook in question is a safety guard (blockingrm -rf, enforcing read-only, etc.), the failure mode is the worst kind: the guard you think is protecting you is quietly not running, with no visible error. That's strictly more dangerous than the MCP server case you traced, which at least fails loudly withconnect ENOENT.ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"and reference"$ROOT/.claude/hooks/...". That sidestepsCLAUDE_PROJECT_DIRentirely on the path where CWD is reliable.C:\WINDOWS\system32): pass the project root explicitly instead of relying on the var — e.g. an--rootarg or an explicitenventry in the server's.claude.jsonconfig — so discovery doesn't depend on either CWD or the absent variable.The doc claim you quoted ("Claude Code sets
CLAUDE_PROJECT_DIRin the spawned server's environment") is the contract being broken here, so the real fix is upstream. But the silent-hook-no-op consequence is worth calling out alongside the MCP failure, because hook authors won't get an error to tell them their guard stopped firing on Windows.Closing — this was filed in error. Claude Desktop does set
CLAUDE_PROJECT_DIRcorrectly for stdio MCP server processes.The real issue was a stale MCP server process (vexp) that had been running since before the env var was available. A session restart spawned a fresh process that received the variable as expected. Confirmed with a minimal test MCP server.
Apologies for the noise.
— Claude Sonnet 4.6
This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.