Skip to content

Support environment variables from direnv when running shells #148

@charles-dyfis-net

Description

@charles-dyfis-net

Background

Software development often requires project-specific tools be added to the PATH. Often, there are toolchains which generate such PATH entries dynamically -- Nix uses flake.nix files (or, following older releases, shell.nix files) to specify which software to download and install if necessary and then place in the PATH; uv, conda, and many other similar tools exist for other ecosystems.

One generic way to use any/all of these tools to update one's shell environment based on the current working directory is to use direnv, which allows a .envrc file to specify how to set up an environment for a given project. Used in an interactive shell, direnv sets up related variables/tools when the user cds into a directory, and reverts to the prior state when that directory is exited.

When Claude Code executes shell commands, it spawns fresh subprocesses that don't benefit from the interactive shell hooks that makes direnv seamless for human developers. Each Bash() tool invocation runs in isolation, without the PROMPT_COMMAND or precmd hooks that normally trigger direnv export.

This means that even when a developer has carefully configured their project's .envrc to set up the correct toolchain, the AI assistant operates in a "bare" environment — potentially using system-default versions of tools, missing project-specific binaries entirely, or lacking environment variables that affect build behavior.


Why This Matters

  1. Correctness: Commands that work in the developer's terminal may fail or behave differently when run by the AI agent
  2. Reproducibility: The AI operates in a different environment than the human, leading to "works for me" situations in reverse
  3. Security: Without proper environment isolation, the AI might inadvertently use or modify global toolchains rather than project-scoped ones

Opportunity

Claude Code already provides the CLAUDE_ENV_FILE mechanism — a script sourced before each Bash command execution. This offers a clean integration point: Maestro could detect .envrc presence and configure CLAUDE_ENV_FILE to invoke direnv export bash, ensuring the AI's shell environment matches what the developer sees interactively.


Proposed Behavior

When spawning an AI agent session, Maestro could:

  1. Detect whether the project directory contains a .envrc file
  2. Verify that direnv is installed and the .envrc is allowed (the user has previously run direnv allow)
  3. Configure CLAUDE_ENV_FILE to source the direnv-exported environment before each command

This should be automatic and require no per-project configuration from the user — if they've already set up direnv for their workflow, Maestro should respect that setup.


User-Facing Considerations

Warnings: When Maestro detects a .envrc but cannot activate it (direnv not installed, or .envrc not yet allowed), a non-blocking notification could inform the user rather than silently falling back to the bare environment.

No action when not applicable: Projects without .envrc would see no change in behavior — this feature should be invisible to users who don't use direnv.


Scope Limitations

This proposal intentionally limits scope to direnv-based workflows. While it would be possible to also detect flake.nix or shell.nix files and invoke Nix directly, direnv already serves as a shared integration layer for these tools (with basic support built in, and extended support available via https://github.com/nix-community/nix-direnv), and supporting direnv alone covers not just Nix but also many other ecosystems (node, opam, julia, php, pip, pyenv -- search for layout in https://direnv.net/man/direnv-stdlib.1.html for a current list) without Maestro needing ecosystem-specific knowledge.


Implementation Notes

The integration point is src/main/ipc/handlers/process.ts, where process:spawn resolves environment variables before invoking ProcessManager.spawn(). The existing effectiveCustomEnvVars flow (lines 172-175) provides a pattern for injecting CLAUDE_ENV_FILE without disrupting other environment configuration.

Detection logic would fit naturally in a new utility module (e.g., src/main/utils/direnv.ts), following the pattern established by src/main/utils/cliDetection.ts for detecting external tools like gh and cloudflared.

Key considerations:

  • Efficiency: direnv export bash is fast (~10-50ms after the environment is cached), so running it per-command via CLAUDE_ENV_FILE adds negligible overhead
  • GC roots (Nix-specific): For Nix users, nix-direnv automatically maintains garbage collection roots in .direnv/, so builds won't be collected while the project exists — no additional handling needed from Maestro
  • Allowed check: direnv status output indicates whether the .envrc is allowed; this avoids attempting activation that would fail
  • SSH sessions: For remote execution, the local .envrc path isn't meaningful — detection could be skipped when sessionSshRemoteConfig is active, or deferred to a future enhancement for remote direnv support

Metadata

Metadata

Assignees

Labels

back burnerWill swing back to this one in the future.enhancementNew feature or request

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions