A standalone Go program that helps you automatically sources .denv.bash files
when you enter a directory. It finds all denv files in the ancestor chain
and loads the allowed ones from outer to inner, nesting shells or iterating a
colon-separated chain.
Inspired by direnv, but simpler: it spawns a new interactive bash shell instead of exporting environment diffs back to the parent shell.
- direnv creates a new bash process, loads
.envrc, and exports the diff back to the parent shell, so aliases and functions cannot be exported (see also GitHub issue). denv simply spawns a new$BASHand lets youcdout to exit. - direnv loads the closest
.envrcfrom the ancestor chain — parent environments are unloaded when entering a subdirectory with its own.envrc. denv finds all.denv.bashfiles in the ancestor chain and loads the allowed ones from outer to inner, letting you compose workspace-wide and project-level settings naturally.
Download the latest binary from the releases
page and put it in your PATH:
curl -fsSL https://github.com/roxma/denv/releases/latest/download/denv_linux_$(uname -m) | sudo tee /usr/local/bin/denv > /dev/null && sudo chmod +x /usr/local/bin/denvOr install with Go:
go install github.com/roxma/denv@latestAdd to your .bashrc:
# Optional: override the default config path (~/.config/denv/denv.json)
# export DENV_CONFIG="$HOME/.config/denv/denv.json"
PROMPT_COMMAND='eval "$(denv prompt)"'Then:
$ mkdir foo && echo 'echo "in foo directory"' > foo/.denv.bash
$ cd foo
denv: spawn /bin/bash
denv: loading [/home/you/foo/.denv.bash]
in foo directory
$ cd ..
denv: exit [/home/you/foo/.denv.bash]$ denv
denv 1.0.0
auto source .denv.bash of your workspace
USAGE:
denv [SUBCOMMAND]
SUBCOMMANDS:
prompt for bashrc: PROMPT_COMMAND='eval "$(denv prompt)"'
allow Grant permission to denv to load the .denv.bash
deny Remove the permission
prune Remove non-existing-file permissions
Generates the shell hook code. If no shell name is given, denv auto-detects the
parent shell. Only bash and sh are supported today.
Grants permission to load all denv files found in the current directory and
its ancestors. Permissions are stored in ~/.config/denv/denv.json.
Loading traverses the ancestor chain from outermost to innermost and stops
at the first denied entry. Allowed ancestors before that point will still
load. Running denv allow once approves every denv file between the current
directory and the filesystem root.
Revokes permission. If path is omitted, denies the .denv.bash for the
current directory. If path is a directory, denies the .denv.bash inside it.
Removes entries pointing to deleted files/directories from the config.
export WORKSPACE_DIR=$(readlink -f "$(dirname "${BASH_SOURCE[0]}")")for.denv.bashto locate its directory.exec bashto reload a modified.denv.bash.
An early Python prototype took ~80 ms — too slow for every prompt. A Go binary starts in under 2ms.
denv allow records the file's ctime (inode change time). Before loading,
denv checks it still matches. Unlike mtime, ctime cannot be set arbitrarily
and is available from stat(2) without reading the file. Any edit, chmod,
or touch changes ctime and requires re-approval.
- Take care of background jobs before leaving an
.denv.bashscope — denv will refuse to load if you have active jobs. - The
.denv.bashcan be a file or a directory. If it's a directory,$DENV_LOAD/denv.bashis sourced instead.
When no .denv/ or .denv.bash is found, denv falls back to .envrc.
Precedence: .denv/ > .denv.bash > .envrc. Set "ignore_envrc": true
in ~/.config/denv/denv.json to disable this.
This project was developed with assistance from DeepSeek, Kimi, kimi-cli, and the opencode tool.