Summary
The desktop app's shell snapshot generation writes export PATH=... using the JavaScript process's PATH (minimal macOS GUI app PATH: /usr/bin:/bin:/usr/sbin:/sbin) instead of the PATH resolved inside the zsh process after sourcing dotfiles (.zshenv, .zshrc, etc.).
This causes tools installed via Homebrew (/opt/homebrew/bin) and /usr/local/bin to be unavailable in Bash tool executions.
Environment
- macOS (Apple Silicon)
- Claude Code Desktop App v2.1.87
- Shell: zsh
Steps to Reproduce
- Install tools via Homebrew (e.g.,
node, gh, npm)
- Add
/opt/homebrew/bin to PATH in ~/.zshenv:
export PATH="/usr/local/bin:/opt/homebrew/bin:$PATH"
- Launch Claude Code desktop app (not terminal)
- Run
which node via Bash tool → command not found
Root Cause
In the snapshot generation code (gW7 / G31 functions), the shell script template includes:
echo "export PATH=${W7([H || ""])}" >> "$SNAPSHOT_FILE"
This evaluates H (a JavaScript variable from process.env.PATH) at template build time, not the PATH that the zsh process resolves after sourcing .zshenv / .zshrc.
The snapshot generation runs zsh -c -l <script>, which does correctly source .zshenv and resolve the full PATH inside zsh. However, that resolved PATH is never captured — the snapshot file gets the JavaScript-side value instead.
Expected Behavior
The snapshot should capture the PATH as resolved inside the zsh process after all dotfiles are sourced, not the parent JavaScript process's PATH.
Current Workaround
Using a SessionStart hook in settings.json to patch the snapshot file after generation:
{
"hooks": {
"SessionStart": [{
"hooks": [{
"type": "command",
"command": "sleep 1; SNAP=$(ls -t ~/.claude/shell-snapshots/snapshot-zsh-*.sh 2>/dev/null | head -1); if [ -n \"$SNAP\" ] && grep -q 'export PATH=/usr/bin' \"$SNAP\" && ! grep -q '/opt/homebrew/bin' \"$SNAP\"; then sed -i '' 's|export PATH=/usr/bin|export PATH=/usr/local/bin\\\\:/opt/homebrew/bin\\\\:/usr/bin|' \"$SNAP\"; fi",
"timeout": 10
}]
}]
}
}
Additional Notes
settings.json's env.PATH setting is also ineffective because the snapshot's export PATH=... overrides it when sourced.
- The terminal version of Claude Code inherits the correct PATH from the parent shell, so this issue is specific to the desktop app.
Summary
The desktop app's shell snapshot generation writes
export PATH=...using the JavaScript process'sPATH(minimal macOS GUI app PATH:/usr/bin:/bin:/usr/sbin:/sbin) instead of the PATH resolved inside the zsh process after sourcing dotfiles (.zshenv,.zshrc, etc.).This causes tools installed via Homebrew (
/opt/homebrew/bin) and/usr/local/binto be unavailable in Bash tool executions.Environment
Steps to Reproduce
node,gh,npm)/opt/homebrew/binto PATH in~/.zshenv:which nodevia Bash tool →command not foundRoot Cause
In the snapshot generation code (
gW7/G31functions), the shell script template includes:This evaluates
H(a JavaScript variable fromprocess.env.PATH) at template build time, not the PATH that the zsh process resolves after sourcing.zshenv/.zshrc.The snapshot generation runs
zsh -c -l <script>, which does correctly source.zshenvand resolve the full PATH inside zsh. However, that resolved PATH is never captured — the snapshot file gets the JavaScript-side value instead.Expected Behavior
The snapshot should capture the PATH as resolved inside the zsh process after all dotfiles are sourced, not the parent JavaScript process's PATH.
Current Workaround
Using a
SessionStarthook insettings.jsonto patch the snapshot file after generation:{ "hooks": { "SessionStart": [{ "hooks": [{ "type": "command", "command": "sleep 1; SNAP=$(ls -t ~/.claude/shell-snapshots/snapshot-zsh-*.sh 2>/dev/null | head -1); if [ -n \"$SNAP\" ] && grep -q 'export PATH=/usr/bin' \"$SNAP\" && ! grep -q '/opt/homebrew/bin' \"$SNAP\"; then sed -i '' 's|export PATH=/usr/bin|export PATH=/usr/local/bin\\\\:/opt/homebrew/bin\\\\:/usr/bin|' \"$SNAP\"; fi", "timeout": 10 }] }] } }Additional Notes
settings.json'senv.PATHsetting is also ineffective because the snapshot'sexport PATH=...overrides it when sourced.