devenv integration
Jotain ships a native integration with devenv, the Nix-based developer environment tool. The library lives inlisp/devenv.el (standalone and reusable — no Jotain dependencies) and
is wired in by init-devenv.el. Everything is driven through the
devenv CLI and degrades to a clean error when the binary is not on
PATH. devenv 2.1+ is recommended; every invocation sets AI_AGENT=1
so devenv’s TUI stays out of the machine-readable output.
Entry point: C-c v
C-c v (or M-x devenv) opens a transient covering the whole surface:
The
-c (--clean) and -r (--refresh-eval-cache) infixes apply to
devenv-test and devenv-build.
Long-running commands (test, build, tasks, update, gc) run in
compilation buffers with Nix trace matching, so M-g M-n jumps to the
file.nix:LINE:COL locations in evaluation errors.
Process dashboard
devenv-processes opens a tabulated view of the devenv process
manager. Keys: s start, k stop, r restart, l/RET logs, u
up, d down, g refresh. Data comes from devenv processes list
(JSON when available, plain table on older devenv), fetched
asynchronously so the UI never blocks.
Environment loading and reload
Environment loading is done by the library’s native loader (devenv-env-global-mode, enabled by init-devenv; direnv/envrc is
disabled in init-prog). Every buffer inside a trusted devenv project
carries the project environment buffer-locally, so eglot servers,
apheleia formatters, M-x compile, and the ghostel terminal all see
devenv-provided tools with no extra configuration.
The goal is parity with a terminal devenv shell, which takes more
than reading devenv print-dev-env:
devenv-env-loader=shell(default) sources the printed dev-env script in bash, shellHook included, and diffs the resulting environment against Emacs’s own. Hook-only values therefore reach Emacs too:LOCALE_ARCHIVE,MANPATH,CARGO_INSTALL_ROOT,$CARGO_HOME/binonPATH, and anythingenterShellexports. Because sourcing runs project code it is limited to projects trusted withdevenv allow(see below).devenv-env-loader=jsonparsesdevenv print-dev-env --jsoninstead. No project code runs, but the shellHook’s effects are missing. This is also the automatic fallback when sourcing is unavailable or fails.PATHandXDG_DATA_DIRSare layered, not replaced (devenv-env-layered-variables), matching thenix_saved_PATHrestore in devenv’s own script. Without this, everything outside the project profile disappears fromexec-path— thedevenvCLI included, which then breaksC-c vinside the very projects it manages.- Derivation-only variables are dropped
(
devenv-env-never-applied-variables): nix’s ownignoreVarsplus the stdenv build variables devenv’s shellHook unsets. The JSON form is the raw build environment, so applying it verbatim setsTMPDIR/NIX_BUILD_TOPto the build sandbox’s/build, which does not exist on the host. That breaks every tool needing a temp directory: rust-analyzer’s proc-macro server dies withproc-macro server did not respond with data, and cargo build scripts fail.
eglot-ensure is deferred and replayed
once the environment lands, so language servers never start with the
wrong PATH. If the fetch fails, the deferred buffers are released
without connecting (starting a server with the login environment would
be the wrong toolchain) and the message says how many are without an
LSP; M-x eglot or C-c v r retries.
After changing devenv.nix, run devenv-reload (C-c v r). It
invalidates the library’s caches, re-fetches the environment, and —
because eglot snapshots the environment when a server starts — offers
to eglot-reconnect every server serving the project so language
servers pick up new PATH entries. Where direnv is in charge instead,
envrc + use devenv is an equally good substrate: set
devenv-env-defer-to-direnv back to t and the native loader stays
out of any project with a .envrc, delegating reload to
envrc-reload-all.
Auto-activation trust (devenv allow / revoke)
devenv 2.1+ gates shell auto-activation behind a per-project trust
database (~/.local/share/devenv/allowed), managed with devenv allow and devenv revoke. The integration honours the same
contract: the native loader (devenv-env-global-mode) consults the
internal devenv hook-should-activate check and never evaluates an
untrusted devenv.nix automatically — exactly like the zsh/fish/nu
shell hooks. The same gate decides whether the environment may be
obtained by sourcing (which runs the project’s shellHook); an untrusted
or unverifiable project falls back to the JSON loader. devenv-allow (C-c v a) trusts the project and
immediately activates the loader in its buffers; devenv-revoke
(C-c v x) untrusts it and drops the native environment. On devenv
versions without the trust model (< 2.1) the loader behaves as
before. Note that the direnv/envrc path has its own consent gate
(envrc-allow → direnv allow), independent of devenv’s database.
Mode-line status
devenv-modeline-mode (global, enabled by init-devenv at startup)
adds a status segment to the mode line for buffers inside a devenv
project:
devenv[on]— the buffer’s environment carries the devenv shell (loaded by envrc/direnv or the native loader; detected viaDEVENV_PROFILE).devenv[off]— devenv project, environment not loaded.devenv[!]— auto-activation trust denied; rundevenv-allow.
mouse-1 on the
segment opens the devenv transient. The redisplay path is
subprocess-free: trust is probed once per project in the background
from find-file-hook and cached.
LSP for devenv.nix
devenv 2.0+ shipsdevenv lsp, a bundled nixd preconfigured with the
project’s devenv module options (completion, hover docs for
devenv.nix options). init-devenv routes devenv.nix and
devenv.local.nix buffers to it while nil keeps serving every other
Nix file — the routing entry delegates non-devenv buffers to whatever
contact was registered before it.
MCP server for the AI stack
devenv 2.0+ shipsdevenv mcp, a stdio MCP server exposing nixpkgs
package search, devenv option search, and process control
(list/status/logs/start/stop/restart). devenv-mcp-setup (C-c v M)
registers it with mcp.el
under a per-project name (devenv-<project>); start it from
M-x mcp-hub and run M-x gptel-mcp-connect to hand its tools to
gptel. devenv-mcp-remove unregisters it.
Customization
Thedevenv customization group covers the executable name
(devenv-executable), the quiet-mode environment
(devenv-extra-env), global CLI flags (devenv-global-arguments),
cache TTL and command timeout, detached up
(devenv-up-detached), and the dashboard’s log line count
(devenv-processes-log-lines).
The native loader adds: how the environment is obtained
(devenv-env-loader), whether direnv keeps ownership
(devenv-env-defer-to-direnv), extra variables to drop
(devenv-env-ignored-variables, filtered on top of the built-in
devenv-env-never-applied-variables), which variables extend rather
than replace the global value (devenv-env-layered-variables), and the
shell bookkeeping ignored when diffing a sourced environment
(devenv-env-shell-ignored-variables).