direnv export and exec for Non-Interactive CI
direnv export bash prints shell commands that set the .envrc variables, so eval "$(direnv export bash)" loads them into a non-interactive CI shell.
Because CI has no prompt hook, you load direnv manually. There are two patterns: eval the export to mutate the current shell, or wrap a command with exec.
What it does
direnv export <shell> evaluates the allowed .envrc and emits the export/unset statements needed to apply it, which you eval. direnv exec <dir> <cmd> instead runs a single command in a child process that already has the env loaded, leaving your shell untouched. Both require the .envrc to be allowed first.
Common usage
# load into the current step's shell
direnv allow
eval "$(direnv export bash)"
./run-tests.sh # sees the .envrc vars
# or wrap one command, no eval needed
direnv exec . ./run-tests.shIn CI
On GitHub Actions each step is a fresh shell, so eval "$(direnv export bash)" only affects that step. To persist a variable to later steps, append it to $GITHUB_ENV after exporting. A clean approach: direnv allow once, then direnv exec . <cmd> per step so each command gets the env without leaking it.
Options
| Command | What it does |
|---|---|
| direnv export bash|zsh|json | Print env changes for the given shell (or JSON) |
| direnv exec <dir> <cmd> | Run cmd with that directory’s env loaded |
| direnv allow | Required once before export/exec will load .envrc |
| DIRENV_LOG_FORMAT= | Set empty to silence direnv log lines in CI |
Common errors in CI
If eval "$(direnv export bash)" sets nothing, the .envrc was never allowed: run direnv allow first (the blocked message goes to stderr, not the eval output). Noisy "direnv: loading"/"direnv: export" lines polluting logs are silenced with export DIRENV_LOG_FORMAT=. Variables disappearing between steps is by design; use $GITHUB_ENV or re-export.