Setting `CLAUDE_CONFIG_DIR` in `.env`? It silently loses to your shell.
Trying to isolate a child process by setting an env var in .env, only to find the value inherited from your shell wins instead. dotenv by default does not overwrite existing process.env values.
This bit me in HelloEDi. I wanted to spawn claude -p with CLAUDE_CONFIG_DIR pointing at an isolated dir so the user's global ~/.claude/CLAUDE.md (which carries a personal persona) wouldn't leak into the assistant. .env had:
CLAUDE_CONFIG_DIR=/home/davidw/.helloedi/claude-home
But my shell already exports CLAUDE_CONFIG_DIR=/home/davidw/.claudeh for my own Claude Code setup. dotenv loaded .env, saw the var already set, did nothing. The server's process.env.CLAUDE_CONFIG_DIR was still the shell's value. The spawned claude happily read the persona from there. The assistant introduced itself with the wrong name.
The fix is to never put the canonical var name in .env when your dev environment is likely to export it. Use a project-internal name and translate on spawn:
# .env
EDI_CLAUDE_CONFIG_DIR=/home/davidw/.helloedi/claude-home
// claudeRunner.js
const dir = process.env.EDI_CLAUDE_CONFIG_DIR ?? defaultDir;
spawn('claude', args, {
env: { ...process.env, CLAUDE_CONFIG_DIR: dir },
});
Now the shell's CLAUDE_CONFIG_DIR stays in process.env (harmless to the parent), .env's EDI_* value is read without collision, and the child gets the right CLAUDE_CONFIG_DIR because we set it explicitly in the spawn env.
dotenv does have { override: true }, but flipping it globally affects every variable in .env — including ones you actually want the shell to win on. The renamed-variable approach is scoped and self-documenting.
The shape of this trap is general: whenever a name in .env overlaps with one your shell may already export, the child sees whichever value won at parent startup, not the one you wrote. Pick names you control, or be explicit at spawn time.