Skip to content

feat(omp): declare direnv preflight settings for agent bash commands - #2886

Merged
cameronraysmith merged 1 commit into
mainfrom
fm/vx-omp-direnv-autoload
Sep 1, 2026
Merged

feat(omp): declare direnv preflight settings for agent bash commands#2886
cameronraysmith merged 1 commit into
mainfrom
fm/vx-omp-direnv-autoload

Conversation

@cameronraysmith

Copy link
Copy Markdown
Owner

What this changes

Two keys added to programs.omp.settings in modules/home/ai/omp/default.nix:

bash:
  direnv: auto
  direnvLoadTimeoutMs: 180000

Mechanism selected, and why

omp ships a direnv preflight in its bash tool. Before running a command it walks up from the command's working directory to the nearest .envrc, runs direnv export json there, and folds the resulting variable diff into the command environment. The code is applyDirenvPreflight in packages/coding-agent/src/exec/bash-executor.ts, called from executeBash in the same file and from the ACP-terminal and PTY branches of packages/coding-agent/src/tools/bash.ts. The two settings that steer it are bash.direnv and bash.direnvLoadTimeoutMs, declared in packages/coding-agent/src/config/settings-schema.ts.

bash.direnv is an enum over auto and off whose default is already auto, so nothing here turns the mechanism on. It is declared anyway for the reason the mnemopi block in the same nix file gives for restating upstream defaults: an upstream flip to off should surface as a diff in this repository rather than as devshell tools quietly disappearing from every bash call.

Three other candidates were considered and rejected. pkgs.direnv could be added to extraPackages the way modules/home/ai/pi/default.nix does, but that only puts the direnv binary on the agent's PATH and still leaves every caller writing direnv exec . by hand. The direnv/index.ts extension listed in modules/home/ai/agent-settings.nix is a pi-lineage extension delivered to pi and atomic; omp carries this natively and does not need it. Wrapping the omp binary so it launches inside a devshell would bind one agent process to one project's environment, which is wrong for an agent that moves between repositories.

Budget chosen, and why

180000 ms, raised from the upstream default of 30000.

The budget has to cover a first export that realises a flake devshell. Measured in a fresh worktree of this repository against an already-populated nix store, the first direnv export json after direnv allow took 7.39 seconds; an export that must fetch or build the devshell closure takes minutes. Every subsequent export, served from the nix-direnv cache, returned in under 10 milliseconds.

A generous ceiling is safe in both directions. The effective budget is the smaller of this value and the command's own deadline — applyDirenvPreflight computes Math.min(timeoutMs, callerTimeoutMs) — so raising it extends the wait only for calls that already carry a long timeout, and a short command cannot be stalled by a cold .envrc. Exceeding the budget is not an error either: loadDirenvEnv returns null and the command runs without the direnv environment. Because the failure mode is a silent loss of the devshell rather than a visible error, a ceiling that comfortably covers a cold build is preferable to a tight one.

Empirical result of the bare-command probe

Run in a fresh disposable worktree at ~/.treehouse/vanixiets-22808a/6/vanixiets, using prek as the probe because it exists only in this repository's devshell and is absent from both the nix profile and a fresh login shell.

Before direnv allow, a bare command -v prek reported not found, .pre-commit-config.yaml did not exist, and direnv export json exited 1 with .envrc is blocked.

After direnv allow in that worktree, with no other change and no nix develop wrapper, a bare command -v prek in an omp bash command resolved to /nix/store/plfgv9vypicf6dkcigm4p8iqyxnqc26n-prek-0.4.10/bin/prek and prek --version printed prek 0.4.10. The second benefit holds as well: .pre-commit-config.yaml appeared as a symlink into the nix store, generated by the devshell through config.pre-commit.devShell at modules/devshells/default.nix:31, and the commit on this branch ran the gitleaks and treefmt hooks through it. This matters because modules/formatting.nix:18 deliberately disables the pre-commit flake check for cache granularity, so the devshell is the only thing that produces that file.

No omp restart was required. Because bash.direnv was already at its default of auto and the preflight runs per bash command rather than at process start, the behaviour appeared on the very next command. A restart will be needed to pick up the raised timeout, since settings load at process start, but that affects only the ceiling and not whether the mechanism runs.

Remaining gap, left for a separate decision

The probe needed one direnv allow, and that is not incidental. loadDirenvEnv honours direnv's own allow list and never auto-allows, by explicit design, so that cloning a repository with a hostile .envrc grants it nothing. Disposable worktrees are created fresh and inherit no approval, so the goal of devshell tools resolving in any clone is not reached by omp settings alone.

Closing it means a whitelist in ~/.config/direnv/direnv.toml, which this repository already manages through programs.direnv.config in modules/home/terminal/direnv.nix. That is a change to the trust boundary of the interactive shell as much as the agent's, and it is not bundled here. A narrow form would whitelist prefixes holding repositories this workspace authors and their worktree pools, and would exclude ~/ghq, which by convention holds third-party repositories that are only read.

Testing

Built .#checks.aarch64-darwin.treefmt and .#checks.aarch64-darwin.home-manager-crs58; both pass. Those are the checks that cover a change to a home-manager module: home-manager-crs58 builds the generated omp-config.yml derivation and the activation script that merges it into ~/.omp/agent/config.yml. The generated file was inspected directly and carries the expected nested bash mapping rather than a flat dotted key. nix eval of config.programs.omp.settings.bash returns {"direnv":"auto","direnvLoadTimeoutMs":180000}.

The rest of the check set was deliberately left out. This change touches one home-manager module and alters no other derivation, so the remaining checks — the other users' home configurations, the darwin and NixOS machine closures, the kubernetes manifests — cannot be affected by it.

@cameronraysmith cameronraysmith self-assigned this Sep 1, 2026
@mergify

mergify Bot commented Sep 1, 2026

Copy link
Copy Markdown

Tick the box to add this pull request to the merge queue (same as @mergifyio queue).

  • Queue this pull request

@cameronraysmith
cameronraysmith force-pushed the fm/vx-omp-direnv-autoload branch from b50bfbc to a092675 Compare September 1, 2026 03:50
omp folds a clone's direnv environment into the bash commands it runs,
which is what lets a devshell-provided tool resolve without wrapping the
call in `nix develop` or `direnv exec`. The mechanism is on by default;
what this sets is the budget it runs under.

`bash.direnvLoadTimeoutMs` goes from the upstream 30000 to 180000. The
first export in a clone has to realise the flake devshell, measured at
7.4 seconds in this repository against an already-populated nix store and
running to minutes when the closure has to be built, while later exports
served from the nix-direnv cache return in under 10 milliseconds. The
higher ceiling cannot stall a short command, because the effective budget
is the smaller of this value and the command's own deadline. Overrunning
it runs the command without the direnv environment rather than failing,
so a tight value loses devshell tools silently.

`bash.direnv` is declared at the value it already defaults to, so that a
change to that default appears as a diff here rather than as devshell
tools disappearing from every bash command.

A clone whose .envrc has not been through `direnv allow` still runs
without the devshell. That gate belongs to direnv's own configuration and
is untouched here.
@cameronraysmith
cameronraysmith force-pushed the fm/vx-omp-direnv-autoload branch from a092675 to 106f539 Compare September 1, 2026 05:37
@cameronraysmith
cameronraysmith changed the base branch from main to fm/vx-atomic-0917-update September 1, 2026 05:37
Base automatically changed from fm/vx-atomic-0917-update to main September 1, 2026 06:01
@cameronraysmith
cameronraysmith merged commit 106f539 into main Sep 1, 2026
9 checks passed
@cameronraysmith
cameronraysmith deleted the fm/vx-omp-direnv-autoload branch September 1, 2026 06:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant