Claude Code runs each Bash command in its own process.
Environment variables don’t persist between them — an export in one invocation is gone by the next.
If your project uses a Nix devShell, this is a problem.
Running nix develop inside a Claude session is messy, and running outside it requires restarting the devShell and Claude for any new dependency added to flake.nix.
CLAUDE_ENV_FILE
Claude Code has a CLAUDE_ENV_FILE environment variable.
Set it to a shell script path before launching, and Claude will source that script before every Bash command it runs.
For a Nix devShell, the script needs to evaluate the flake and export all its variables.
nix print-dev-env --json does exactly that — it outputs the full devShell environment as structured JSON without spawning a shell:
# claude-env-file.sh
source <(nix print-dev-env --json | python3 -c "
import json, sys, shlex
SKIP = {'LINENO', 'EPOCHSECONDS', 'EPOCHREALTIME', 'BASH_ENV', 'PS1'}
data = json.load(sys.stdin)
for name, info in data['variables'].items():
if name in SKIP:
continue
t = info.get('type')
if t == 'exported':
print(f'export {name}={shlex.quote(info[\"value\"])}')
elif t == 'var':
print(f'{name}={shlex.quote(info[\"value\"])}')
elif t == 'array':
vals = ' '.join(shlex.quote(v) for v in info[\"value\"])
print(f'export {name}=({vals})')
")
SKIP filters out shell-internal variables that cause problems when injected into a foreign process.
shlex.quote handles paths with spaces or special characters safely.
Then launch Claude with:
CLAUDE_ENV_FILE=./claude-env-file.sh claude
Claude evaluates the devShell before every Bash command it runs, so new packages added to flake.nix are available immediately without relaunching anything.
The downside: nix print-dev-env has to evaluate your flake each time, which adds a few seconds per command. For small shells it’s fine. For large ones it gets noticeable.