Claude Desktop passes only PATH from the login shell to Claude Code sessions; every other variable is dropped

Status Open
Reported on v2.1.258
Maintainer reply None cached
Activity 0 comments · opened Sep 6, 2026

Summary

On macOS, a Claude Code session started from the Claude Desktop app has a fully repaired PATH but none of the other environment variables the login shell exported. Tools therefore resolve while the credentials and paths they need are unset, so failures surface far from the cause — a package restore that 401s, a script that raises KeyError, a query that silently returns the wrong scope.

The app already does the expensive part: its disclaimer helper runs a login shell. Only PATH survives from it. Keeping the rest would fix hooks, stdio MCP servers and tool-invoked commands together, with no per-machine configuration.

Environment

  • macOS 26.6.2 (arm64)
  • Claude Desktop, bundled CLI 2.1.258
  • Compared against Claude Code 2.1.260 from a terminal, the VS Code extension 2.1.260, Rider's ACP agent, and Zed's external agent, on the same machine within the same few minutes

Reproduce

  1. Add a marker to ~/.zprofile (not ~/.zshrc — the distinction matters):

``bash
export MARKER_FROM_ZPROFILE=1
``

  1. Start a Claude Code session from the Claude Desktop app, and ask it to run echo "${MARKER_FROM_ZPROFILE:-UNSET}" and echo "$PATH".
  2. Do the same from a terminal-launched claude.

Expected: both report the marker set. Actual: the Desktop session reports UNSET, while its PATH is unmistakably the login shell's — it contains directories that only ~/.zprofile adds. So the login shell ran and its environment was discarded except for PATH.

Measured

Walking the process tree with ps eww, counting exported variables (names only). The eight probed variables are credentials and paths exported by ~/.zprofile on this machine.

| Process | Variables | PATH | The 8 probed |
| -- | --: | -- | -- |
| Claude.app (main) | 14 | 4-directory default | 0/8 |
| Claude.app/Contents/Helpers/disclaimer | 45 | full, login-shell PATH | 0/8 |
| claude (the session) | 45 | full | 0/8 |
| Terminal-launched claude | 67 | full | 8/8 |

Process chain of the affected session:

claude <- disclaimer <- Claude <- launchd

The 14 on the main process is launchd's environment, which is what every macOS GUI app starts with — that row is the control confirming the app was launched normally. The jump from 14 to 45 at disclaimer is the login shell's PATH being merged in, and nothing else.

Anything the session spawns inherits the same stripped environment — hooks and stdio MCP servers included, since claude spawns those directly.

Other IDE integrations do not do this

Measured on the same machine, same method:

| Route | Variables | The 8 probed |
| -- | --: | -- |
| VS Code extension 2.1.260 (claudeCode.useTerminal: false) | 72 | all probed set |
| Rider ACP agent | 94 | all probed set |
| Zed external agent | 58 | all probed set |

The VS Code case is the closest comparison: its main process shows the same bare 14 variables as Claude.app, and the extension host it spawns has the full environment, which the claude process then inherits:

claude <- Code Helper (Plugin) <- Code <- launchd

Same starting point, same one-login-shell cost, different result — the whole environment is passed on rather than one variable.

A note for whoever reproduces this

Do not launch the app from a terminal to test it. open -a "<app>" passes the calling shell's environment to the app, so the app starts with a full environment and the bug appears fixed. Launch from Dock/Spotlight, and check the control first: the main process must show ~14 variables and the four-directory PATH. Anything much larger means the launch was contaminated and the measurement is meaningless. This cost us one full measurement before we caught it.

Suggested fix

In the disclaimer helper, pass on the entire environment returned by the login shell it already runs, rather than extracting PATH alone.

Related

The same class of problem has been reported on the other two desktop platforms, which suggests the environment handed to child processes is worth looking at across all three rather than only here:

  • #90074 — Windows desktop: stdio MCP servers receive a ~16-variable sanitized allowlist instead of the user's environment.
  • #82890 — Linux desktop: MCP subprocesses spawned without DISPLAY/ XAUTHORITY.

This report is the macOS case, and differs in an instructive way: the login shell is run, so the correct environment is available in-process — only PATH is carried forward from it.

View original on GitHub ↗