Skip to content
Latchkey

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

bash
# 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.sh

In 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

CommandWhat it does
direnv export bash|zsh|jsonPrint env changes for the given shell (or JSON)
direnv exec <dir> <cmd>Run cmd with that directory’s env loaded
direnv allowRequired 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.

Related guides

Run this faster and cheaper on Latchkey managed runners. Start free →