Documentation Type
Missing documentation (feature not documented)
Documentation Location
No response
Section/Topic
direnv, shell, nix, devbox
Current Documentation
Did not explain how to set up claude with direnv, devbox or nix shell
What's Wrong or Missing?
Did not explain how to set up claude with direnv, devbox or nix shell
Suggested Improvement
Summary
Claude Code's Bash tool runs in a non-interactive shell that doesn't source ~/.bashrc, so direnv and devbox environments are not loaded automatically. This guide provides a working hook-based solution that simulates interactive shell behavior — loading and unloading project environments as Claude changes directories.
Related: #2110 (closed)
The problem
- Claude Code does not source
~/.bashrc when running bash commands
direnv hook bash installs a PROMPT_COMMAND hook — purely interactive, never fires
devbox global shellenv is evaluated in ~/.bashrc — also never runs
- Result: project-specific tools (compilers, linters, runtimes from
.envrc / devbox) are invisible to Claude
Claude does inherit the parent shell's environment, so global devbox works if you launch claude from a terminal. But switching between project directories with different .envrc files does not update the environment.
Solution
Two files: a hook script and a settings.json entry.
~/.claude/hooks/devbox-and-direnv.sh:
#!/bin/bash
# Simulates interactive shell behavior for devbox global + direnv.
# Works for any combination: no devbox, global only, project-specific,
# with or without direnv. Safe no-op on machines without these tools.
[ -n "$CLAUDE_ENV_FILE" ] || exit 0
has_cmd() { command -v "$1" >/dev/null 2>&1; }
ENV_SNAPSHOT="${CLAUDE_ENV_FILE}.snapshot"
# Append source line to CLAUDE_ENV_FILE only once (it's append-only, shared across hooks)
if ! grep -qF "$ENV_SNAPSHOT" "$CLAUDE_ENV_FILE" 2>/dev/null; then
echo ". \"$ENV_SNAPSHOT\"" >> "$CLAUDE_ENV_FILE"
fi
# Generate the snapshot — subshell captures all output, overwrites the file
(
# Load devbox global if: binary exists, global config exists, not already in PATH
GLOBAL_DEVBOX_PROFILE="${HOME}/.local/share/devbox/global/default/.devbox/nix/profile/default/bin"
if has_cmd devbox \
&& [ -f "${HOME}/.local/share/devbox/global/default/devbox.json" ] \
&& [[ ":${PATH}:" != *":${GLOBAL_DEVBOX_PROFILE}:"* ]]; then
_devbox_global=$(devbox global shellenv 2>/dev/null)
if [ -n "$_devbox_global" ]; then
eval "$_devbox_global"
printf '%s\n' "$_devbox_global"
fi
fi
# Load directory-specific env via direnv (handles .envrc with devbox, nix, etc.)
if has_cmd direnv; then
direnv export bash 2>/dev/null
fi
# IMPORTANT: safety guard — see "Gotchas" section below
echo "true"
) > "$ENV_SNAPSHOT"
If you only use direnv (no devbox), simplify to:
#!/bin/bash
[ -n "$CLAUDE_ENV_FILE" ] || exit 0
ENV_SNAPSHOT="${CLAUDE_ENV_FILE}.snapshot"
if ! grep -qF "$ENV_SNAPSHOT" "$CLAUDE_ENV_FILE" 2>/dev/null; then
echo ". \"$ENV_SNAPSHOT\"" >> "$CLAUDE_ENV_FILE"
fi
(
direnv export bash 2>/dev/null
echo "true"
) > "$ENV_SNAPSHOT"
~/.claude/settings.json (add to existing settings):
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "bash ~/.claude/hooks/devbox-and-direnv.sh || true"
}
]
}
],
"CwdChanged": [
{
"hooks": [
{
"type": "command",
"command": "bash ~/.claude/hooks/devbox-and-direnv.sh || true"
}
]
}
]
}
}
How it works
direnv export bash works in non-interactive shells — outputs export statements for the current directory's .envrc, or nothing if no .envrc exists
- SessionStart hook loads the env for the directory where claude was launched
- CwdChanged hook reloads the env whenever Claude changes directory
- A snapshot file (
${CLAUDE_ENV_FILE}.snapshot) is overwritten on each invocation, ensuring clean transitions between projects
- CLAUDE_ENV_FILE itself only gets a single
. "/path/to/snapshot" line appended (it's append-only)
Gotchas
1. ; && syntax error — the echo "true" guard
direnv export bash outputs lines ending with ;:
export FOO=$'bar';
export BAZ=$'qux';
Claude Code inlines CLAUDE_ENV_FILE content into command strings joined with &&. A trailing ; before && creates ; && — a bash syntax error. The echo "true" at the end ensures the last inlined token is true, making ; true && valid.
2. ~/.claude/session-envs/ cache
Claude Code caches environment variables from previous sessions in ~/.claude/session-envs/. If a previous session ran commands with direnv-loaded env, those cached exports (containing ;) get replayed in new sessions with the same ; && problem.
Fix: delete ~/.claude/session-envs/ and restart claude.
3. CLAUDE_ENV_FILE is append-only
Multiple hooks share one CLAUDE_ENV_FILE per session. Using > would clobber other hooks' writes. The snapshot indirection solves this: CLAUDE_ENV_FILE gets one append, the snapshot is freely overwritten.
4. Don't forget direnv allow
If a project's .envrc hasn't been allowed, direnv export bash silently outputs nothing. Run direnv allow in the project directory first.
Tested transitions
| From |
To |
Result |
~/ (no .envrc) |
project with go |
go available |
| project with go |
project with jq |
go gone, jq available |
| any project |
non-project dir |
project env fully unloaded |
| launched from project dir |
(stay) |
env inherited, hook is no-op |
Environment
- Ubuntu, Claude Code 2.1.89
- devbox 0.17.0 (global mode)
- direnv (system package)
- Should work on any Linux/macOS with bash
Impact
High - Prevents users from using a feature
Additional Context
No response
Documentation Type
Missing documentation (feature not documented)
Documentation Location
No response
Section/Topic
direnv, shell, nix, devbox
Current Documentation
Did not explain how to set up claude with direnv, devbox or nix shell
What's Wrong or Missing?
Did not explain how to set up claude with direnv, devbox or nix shell
Suggested Improvement
Summary
Claude Code's Bash tool runs in a non-interactive shell that doesn't source
~/.bashrc, so direnv and devbox environments are not loaded automatically. This guide provides a working hook-based solution that simulates interactive shell behavior — loading and unloading project environments as Claude changes directories.Related: #2110 (closed)
The problem
~/.bashrcwhen running bash commandsdirenv hook bashinstalls aPROMPT_COMMANDhook — purely interactive, never firesdevbox global shellenvis evaluated in~/.bashrc— also never runs.envrc/ devbox) are invisible to ClaudeClaude does inherit the parent shell's environment, so global devbox works if you launch
claudefrom a terminal. But switching between project directories with different.envrcfiles does not update the environment.Solution
Two files: a hook script and a settings.json entry.
~/.claude/hooks/devbox-and-direnv.sh:If you only use direnv (no devbox), simplify to:
~/.claude/settings.json(add to existing settings):{ "hooks": { "SessionStart": [ { "hooks": [ { "type": "command", "command": "bash ~/.claude/hooks/devbox-and-direnv.sh || true" } ] } ], "CwdChanged": [ { "hooks": [ { "type": "command", "command": "bash ~/.claude/hooks/devbox-and-direnv.sh || true" } ] } ] } }How it works
direnv export bashworks in non-interactive shells — outputsexportstatements for the current directory's.envrc, or nothing if no.envrcexists${CLAUDE_ENV_FILE}.snapshot) is overwritten on each invocation, ensuring clean transitions between projects. "/path/to/snapshot"line appended (it's append-only)Gotchas
1.
; &&syntax error — theecho "true"guarddirenv export bashoutputs lines ending with;:Claude Code inlines CLAUDE_ENV_FILE content into command strings joined with
&&. A trailing;before&&creates; &&— a bash syntax error. Theecho "true"at the end ensures the last inlined token istrue, making; true &&valid.2.
~/.claude/session-envs/cacheClaude Code caches environment variables from previous sessions in
~/.claude/session-envs/. If a previous session ran commands with direnv-loaded env, those cached exports (containing;) get replayed in new sessions with the same; &&problem.Fix: delete
~/.claude/session-envs/and restart claude.3. CLAUDE_ENV_FILE is append-only
Multiple hooks share one CLAUDE_ENV_FILE per session. Using
>would clobber other hooks' writes. The snapshot indirection solves this: CLAUDE_ENV_FILE gets one append, the snapshot is freely overwritten.4. Don't forget
direnv allowIf a project's
.envrchasn't been allowed,direnv export bashsilently outputs nothing. Rundirenv allowin the project directory first.Tested transitions
~/(no .envrc)Environment
Impact
High - Prevents users from using a feature
Additional Context
No response