# asdf-direnv does asdf version resolution once instead of on every command

> asdf-direnv is an asdf plugin that installs direnv and wires it into your shell, so the tool versions in a project are resolved when you enter the directory rather than on every shim invocation, which also removes the need to reshim after an npm install. The project is Apache-2.0 licensed, and its own README now marks it as archived in favour of the Go rewrite of asdf or mise.

**asdf-community/asdf-direnv** — direnv plugin for the asdf version manager [maintainer=@jfly, @michi-zuri, @vic]

- Repository: https://github.com/asdf-community/asdf-direnv
- Website: https://github.com/asdf-vm/asdf
- Stars: 571 · Forks: 41
- Language: Shell
- License: Apache-2.0
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/asdf-community-asdf-direnv

## The problem with shims

asdf version resolution is slow, which makes every command execution pay that penalty. That single sentence opens the plugin's motivation section, and everything else in the README is a consequence of it.

The mechanics are worth spelling out, because the cost is not obvious until you trace it. asdf manages multiple versions of command-line tools, and when you install one it creates shims, which are tiny wrappers that forward execution to the real versioned executables. That way asdf has a single shims directory on your PATH and has no need of mangling the PATH for every installed version.

When you run an asdf-managed command such as node, you actually execute an asdf shim, which determines the node version to activate according to your .tool-versions file. The downside is that every single time you run node, asdf has to determine again which version to use, even if you have not changed .tool-versions since the last run. And that happens for every shim execution, which has to traverse directories looking for a .tool-versions file and probably also for legacy version files.

The README lists a second problem, and it is the one that bites hardest in practice. If a command tries to find itself by name in PATH, for example using which my-command, it finds the asdf shim executable and not the actual executable delegated to by asdf. If the command then uses that location as an installation root to find auxiliary files, the shim masks the real executable and things break in ways that are hard to trace.

The third problem is the reshim question. If asdf installed a package manager such as npm, hex, gem or cargo, any new binaries those tools install will not be on PATH unless you run asdf reshim. The reason is that asdf has no way of knowing what npm install does, so it is not until asdf reshim that it figures out new executables are available and creates shims for them.

And the fourth is the mess left behind: some packages come with tons of system tools that shadow those already installed on your system, and having them installed but not activated leaves dead shims all over the place.

## The solution: resolve once, load on directory change

The solution section states the idea in one line: perform asdf version resolution only once and defer environment loading to direnv.

The mechanism is that direnv is an environment-variables manager in the way asdf is a tools manager. It can update your shell environment upon directory change and clean it up when you leave that directory.

This asdf-direnv plugin lets you install direnv and also provides a tiny script to integrate both, allowing direnv to manage any variables exposed by asdf tools, primarily the PATH environment, but also any other variable exposed by your plugin, for example MIX_HOME exposed by the asdf-elixir plugin.

So the resolution happens in direnv when you enter the project directory rather than in a shim on every invocation. The consequences are the four problems above, addressed one by one. Running node invokes the real asdf-managed executable in PATH rather than the shim, which improves speed since version resolution is out of the way and is made only once by direnv upon entering the project directory. Commands that look themselves up in PATH find their expected location. And there is no more reshim needed after an npm install, because the environment is rebuilt from the tool versions rather than from a list of shims that has to be kept in step.

The dead shim problem is the one that goes away quietly. If the environment is derived from the versions in use rather than from a persistent list of shims, then a tool that is not activated does not leave wrappers behind.

## Setting it up

The prerequisites are short: curl and git.

Then the setup is two commands, run once per shell you use:

```bash
asdf plugin-add direnv
asdf direnv setup --shell bash --version latest
```

The README says to install this plugin and run the setup command for all of your preferred shells, bash, fish or zsh, since the --shell flag takes the shell name.

The --version flag is where the choice lives. If you already have a direnv installation, you can specify --version system, and otherwise the plugin can install direnv for you, where you specify either --version latest or a specific direnv release as shown by asdf list-all direnv.

Two practical notes follow. The setup will hint which files were modified, so you might want to review its changes, and after setup you need to close and open your terminal for the hook to take effect.

The system option has a real advantage, because asdf-direnv does not own the direnv binary in that case. When you update direnv through your system package manager, the plugin uses the updated version, and when the version is managed by the plugin you update it by re-running asdf direnv setup --version latest --shell ..., with the optional --no-touch-rc-file flag to stop the shell rc file being modified during the update. There is also an environment variable for making that permanent, ASDF_DIRENV_NO_TOUCH_RC_FILE=1.

For the shell rc file question in general, the README flags that the setup modifies your rc files and asks you to review what changed. That is worth doing once, since the plugin writes the hook into the file your shell reads at startup.

## Per-project and one-shot environments

Once direnv is hooked into your system, the per-project workflow is a single command in the project root directory:

``` bash
asdf direnv local [<tool> <version>]...
```

That writes the environment for the project, and the effect is that entering the directory sets up the environment and leaving it cleans it up.

The second mode is the one for people who do not want to leave files behind. The README describes it as temporary environments for one-shot commands, for the times you just want to execute a one-shot command under a certain environment without creating or modifying .envrc and .tool-versions files in your project directory. The command is asdf direnv shell, and it works in two shapes:

``` bash
# Enter a new shell having python and node
$ asdf direnv shell python 3.8.10 nodejs 14.18.2

# Just execute a npx command under some node version.
$ asdf direnv shell nodejs 14.18.2 -- npx create-react-app
```

The first form opens a shell with those tool versions active, and the second form, with the -- separator, runs a single command under them. The second is the useful one for one-off work, since it needs no project directory and leaves nothing behind.

There is also a configuration switch for a specific failure mode. By default asdf-direnv will fail if a plugin is not installed, but it is possible to change this using the environment variable ASDF_DIRENV_IGNORE_MISSING_PLUGINS=1. That matters in a directory that happens to contain a .tool-versions naming a tool you have not installed, where the default behaviour is to stop rather than to silently run with something else.

Updating the plugin itself is the same as any asdf plugin, with asdf plugin update direnv.

## The archive notice and what replaced it

The most important part of the README is the caution block at the top, and it is easy to miss because it is formatted as a callout rather than as prose.

This project is archived. The asdf ecosystem has evolved since this project was created, and there are better alternatives.

The two alternatives are named. If all you care about is speed, use the go rewrite of asdf, from version 0.16 onward according to the linked changelog. If you want to avoid shims for non-speed reasons, such as confusing which output, mise is an alternative front end to the asdf plugin ecosystem, and it supports PATH activation.

That second point is worth expanding, because it is the reason this plugin existed and the reason the replacement is a better fit for some people. The plugin's whole purpose was to stop shims from masking the real executable. The Go rewrite of asdf addresses the speed problem directly by making resolution fast enough to keep doing it per invocation. mise addresses the which output problem by having a PATH activation mode, which is the same idea as what this plugin did, arrived at from the other direction.

So the recommendation is not that the idea was wrong but that the ecosystem caught up in two different ways, and the plugin no longer needs to exist.

The version history supports that reading. The tagged releases are v0.3.0 from 2022-04-03, v0.2.0 from 2022-03-16 and 0.1.0 from 2020-08-22, so the last release is more than four years old, while the last push to the repository was on 2026-06-19, which is consistent with a repository that is still being touched but is no longer released.

The licence is Apache-2.0, the primary language is Shell, and the repository is under the asdf-community organisation with maintainers listed as jfly, michi-zuri and vic.

## How the project is tested

The Makefile is small and shows how this kind of plugin is normally verified.

```
all: test lint fmt format-check
```

The four targets are bats tests, shellcheck, shfmt formatting and a format check, and the source file list is computed from git:

```
SH_SRCFILES = $(shell git ls-files "bin/*" "*.bash" "*.bats")
```

That glob tells you the layout. The bin/ directory holds the plugin's executables, and the test/ directory holds bats tests. The lib/ directory holds the shared shell code.

The test target runs bats in TAP format:

```
test:
	env TERM=xterm bats -F tap test
```

Setting TERM=xterm pins the terminal type in the test environment; the README does not document the reason, but it is a common way to keep terminal-dependent output stable in tests. The README does not explain the setting, but pinning the terminal type is a common way to keep output-dependent assertions stable in test runners.

shellcheck over the same file list and shfmt with the -s simplification flag means the project treats shell as a language worth linting properly rather than as a set of glue scripts. The.editorconfig and the CHANGELOG.md and CONTRIBUTING.md at the root complete a conventional layout for a plugin in the asdf ecosystem, and the top level also has a.github/ directory for the CI workflows and the build history badge points at buildstats.info for the master branch.

Since the project is archived, this test setup is best read as a description of how the plugin was maintained rather than as a commitment to keep it maintained.

## Conclusion

asdf-direnv suits someone who already uses asdf and has hit the specific problems the plugin was written to fix, since the design is a clean answer to shim overhead, reshimming and commands that resolve their own location, and the setup is two commands. It is the wrong tool for a new setup, because the README carries a caution that the project is archived and points to the Go rewrite of asdf from 0.16 and to mise as alternatives, and the last tagged release here is v0.3.0 from 2022. Before you install it, read the caution at the top of the README and check which of the two successors covers the reason you would have wanted this plugin.

## FAQ

### What does asdf-direnv do?

It is a direnv plugin for the asdf version manager that installs direnv and provides a small script integrating the two. Asdf version resolution is performed only once, when direnv loads the environment on entering a project directory, instead of on every shim invocation.

### Is asdf-direnv still maintained?

The README carries a caution that the project is archived, that the asdf ecosystem has evolved, and that there are better alternatives. It points to the Go rewrite of asdf from 0.16 for speed and to mise with PATH activation for avoiding shims for reasons such as confusing which output. The last tagged release is v0.3.0 from 2022-04-03, and the last push was on 2026-06-19.

### How do I install and set up asdf-direnv?

With curl and git installed, run asdf plugin-add direnv followed by asdf direnv setup --shell bash --version latest, repeating the setup for each shell you use such as fish or zsh. Use --version system if you already have direnv installed. The setup modifies your shell rc file, so review the hinted changes and reopen your terminal afterwards.

### How do I run a one-off command under a specific tool version?

Use asdf direnv shell. For example asdf direnv shell nodejs 14.18.2 -- npx create-react-app executes a single command under that version, while asdf direnv shell python 3.8.10 nodejs 14.18.2 opens a new shell with those versions active. Neither form creates or modifies .envrc or .tool-versions files.

### Why does asdf-direnv avoid asdf reshim?

Because the environment is rebuilt from the tool versions in use rather than from a maintained list of shims. The README notes that asdf has no way of knowing what an install command such as npm install does, so new binaries only appear on PATH after asdf reshim, and that step disappears when direnv manages the PATH.

### What happens if a plugin named in a project is not installed?

By default asdf-direnv fails in that case. The behaviour can be changed with the environment variable ASDF_DIRENV_IGNORE_MISSING_PLUGINS=1, which is what you would set if a directory contains a .tool-versions entry for a tool you do not have installed.

## Sources

- [asdf-community/asdf-direnv on GitHub](https://github.com/asdf-community/asdf-direnv)
- [License: Apache-2.0](https://github.com/asdf-community/asdf-direnv/blob/master/LICENSE)
- [Project website](https://github.com/asdf-vm/asdf)
- [README](https://github.com/asdf-community/asdf-direnv/blob/master/README.md)
- [Releases](https://github.com/asdf-community/asdf-direnv/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/asdf-community-asdf-direnv
