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.