| name | bash-tool-path |
| description | Safely add a directory to Claude Code's Bash tool PATH. Use when a binary runs in your terminal but is "command not found" inside Claude Code's Bash tool, when adding a directory to PATH for Claude Code, when configuring CLAUDE_ENV_FILE, or when settings.json env.PATH did not work. Explains why env.PATH replaces rather than appends PATH and is not variable-expanded, why ~/.zshrc PATH exports are dropped, and the supported SessionStart hook fix. |
Context
- Worked example hook: ~/.dots/agents/hooks/session-start-path.sh
- Existing SessionStart hooks (user): !
grep -n SessionStart ~/.claude/settings.json 2>/dev/null | head -10
- env.PATH in user settings (a footgun if present): !
grep -n '"PATH"' ~/.claude/settings.json 2>/dev/null | head -5
When to use this skill
Use this when a command that works in an interactive terminal fails with
"command not found" inside Claude Code's Bash tool, or when the user wants to
add a directory (a go/bin, cargo, asdf shim, or custom tool dir) to the PATH
that Claude Code's Bash tool sees.
Why the obvious fixes do not work
Three footguns make this harder than it looks. State them plainly so nobody
re-discovers them by hand.
-
settings.json env values are set literally. Claude Code does not
expand $VAR or ${VAR} in them. So "PATH": "${PATH}:/x/bin" sets PATH
to that literal, broken string, not to the inherited PATH plus a directory.
-
env.PATH replaces the inherited PATH wholesale. It cannot append,
and replacing it drops the directories Claude Code already needs (for
example plugin bin dirs). So env.PATH is the wrong tool for adding a dir.
-
The Bash tool sources ~/.zshrc (or ~/.bashrc/~/.profile) at session
start only to capture aliases, functions, and shell options. PATH
export lines in ~/.zshrc or ~/.zshenv are not captured. Claude Code
builds the Bash tool PATH from the process that launched it (often a daemon),
not from your interactive shell, so daemon-launched sessions can lack
go/bin and similar dirs even though they resolve fine in your terminal.
The supported fix: a SessionStart hook that writes to CLAUDE_ENV_FILE
Claude Code sources the file named by $CLAUDE_ENV_FILE before every Bash
command. Because the file is sourced, $PATH expands at source time, so you
can prepend a directory and preserve the inherited PATH. Populate that file
from a SessionStart hook. $CLAUDE_ENV_FILE is available to the
SessionStart, Setup, CwdChanged, and FileChanged hooks.
A static alternative exists — point CLAUDE_ENV_FILE at a fixed shell script
before launching Claude Code — but the SessionStart hook is the portable,
dynamic choice and is what this repo ships.
Steps
-
Write (or extend) a SessionStart hook script that appends an export line
per directory to $CLAUDE_ENV_FILE. Read the worked, tested reference
first: ~/.dots/agents/hooks/session-start-path.sh. It is idempotent,
guards that each directory exists, and prepends rather than clobbers. Mirror
its shape rather than writing a new pattern from scratch. The core line is:
export PATH="/your/dir:$PATH"
Keep $PATH literal (escaped or single-quoted in the heredoc/printf) so it
expands when the env file is sourced, not when the hook runs.
-
Make the hook a no-op when $CLAUDE_ENV_FILE is unset, and append each
export only if the file does not already contain it (the file persists
across repeated SessionStart fires within a session).
-
Register the hook in settings.json under hooks.SessionStart, invoked via
bash. In this repo, registration is handled by dots install agents; for
a standalone setup, add the SessionStart entry to ~/.claude/settings.json.
Use the /update-config skill for the settings.json edit mechanics.
-
Verify in a fresh session: run the bare command name in the Bash tool and
confirm it resolves.
What NOT to do
- Do not set
env.PATH in settings.json to add a directory (it replaces, and
is not variable-expanded — see footguns 1 and 2).
- Do not add the PATH export only to
~/.zshrc/~/.zshenv and expect the
Bash tool to pick it up (footgun 3).
References