Retry/refresh the shell snapshot when a Bash tool command fails with "command not found" for a binary that exists on PATH in an interactive shell (nvm/fnm/asdf-style version managers)

Status Open
Reported on v2.1.233
Maintainer reply None cached
Activity 0 comments · opened Aug 25, 2026

Feature Request

Retry / refresh the shell snapshot when a Bash tool command fails with "command not found" for a binary that demonstrably exists on the user's PATH in an interactive shell (nvm/fnm/asdf-style version managers)

Summary

Claude Code captures a one-time "snapshot" of the shell environment (~/.claude/shell-snapshots/snapshot-zsh-*.sh) at session start, and every subsequent Bash tool call sources that same static file instead of re-reading the user's shell config. If the snapshot is captured before an async-activated PATH manager (nvm, fnm, asdf, pyenv-via-eval, etc.) has finished injecting its shim directory into the parent shell's PATH, every Bash tool call for the rest of the session inherits that incomplete PATH — even though the binaries are genuinely installed and normally reachable from an interactive terminal.

Why this happens (root cause, distinct from #31799)

  • Version managers like fnm/nvm are commonly activated via eval "$(fnm env ...)" placed in ~/.zshrc. ~/.zshrc is only sourced for interactive shells, and the eval line dynamically prepends a per-process temp directory (e.g. ~/.local/state/fnm_multishells/<pid>_<ts>/bin) to PATH.
  • Claude Code's snapshot appears to be captured from the state of the shell that launched the session, at whatever point that capture happens to run. If the capture races ahead of (or otherwise misses) the user's .zshrc eval, the resulting snapshot's PATH is the plain system PATH, with no version-manager shim directory.
  • Because the same snapshot file is resourced verbatim for every later Bash call in the session, the omission isn't a one-off — it's permanent for the rest of that session, even though the exact same terminal, five seconds later, would show the binary correctly on PATH.
  • This is a timing/race problem, not the $PATH-literal-string expansion bug reported in #31799 (which is about the snapshot's PATH variable containing an unexpanded $PATH token). Here the shim directory is simply absent from the snapshot's PATH value entirely — nothing to expand, it was never captured.

Steps to Reproduce

  1. Use fnm (or nvm) for Node, with the standard activation line in ~/.zshrc only (not ~/.zshenv):

``zsh
eval "$(fnm env --use-on-cd --shell zsh)"
``

  1. Open a new terminal tab/window and, in the same instant, launch claude before .zshrc has had time to fully evaluate fnm env (this is more likely to reproduce on a fresh terminal launch, or under system load).
  2. In the new Claude Code session, run a Bash tool command that needs a version-managed binary, e.g. node -v or a project tool that shells out to node.
  3. Observe command not found, even though the same binary resolves fine if you type the identical command directly into the terminal that launched the session.
  4. Confirm via cat ~/.claude/shell-snapshots/snapshot-zsh-*.sh | grep PATH — the version manager's shim directory is absent from the exported PATH.

Impact

  • Silent, session-long failures for any workflow that depends on a version-managed language runtime (Node/npm/npx, and by extension any CLI installed via npm, e.g. this project's use of a local search tool qmd).
  • Non-deterministic: the same machine, same dotfiles, same project can work perfectly in one session and fail in the next, depending purely on timing at session launch. This makes it hard for users to self-diagnose — nothing in their configuration actually changed.
  • The only current workaround is to manually prepend the version manager's activation (eval "$(fnm env --shell bash)" &&) to every single Bash tool command that needs the runtime, for the entire session — brittle, easy to forget, and doesn't fix future sessions.

Proposed Solutions (any of these would help; ranked by how much they'd fix vs. just mitigate)

  1. Retry once on "command not found": if a Bash tool invocation fails with a shell "command not found" error, attempt to refresh/re-derive the snapshot (e.g., by sourcing the user's interactive profile fresh, or re-running whatever mechanism originally produced the snapshot) and retry the command once before surfacing the failure.
  2. Expose a manual snapshot-refresh mechanism: a slash command (e.g. /refresh-shell) or a documented way to force Claude Code to recapture the shell snapshot mid-session, so users/agents who diagnose a stale-PATH issue can fix it without restarting the whole session.
  3. Document the interaction with async PATH managers: Claude Code's docs could note that version managers activated only in ~/.zshrc (interactive-only) are vulnerable to this race, and recommend moving activation to ~/.zshenv (sourced for every shell, interactive or not) for reliable behavior with the Bash tool. This doesn't fix the race but gives users an informed way to avoid it.

Environment

  • Claude Code version: 2.1.233 (native binary)
  • OS: macOS 15.5 (Sequoia), Apple Silicon
  • Shell: zsh, version manager: fnm 1.39.0
  • Installation: Homebrew (fnm), native Claude Code install

Related issues

  • #31799 (closed/stale) — different root cause (literal $PATH not expanded in the snapshot), but same symptom class: Bash tool PATH silently diverges from the interactive shell's real PATH. Worth cross-referencing since both point at the snapshot mechanism as the common source of PATH-related surprises.

View original on GitHub ↗