mise: bridge per-directory env into agent shells via a managed .envrc (direnv) #270
Labels
No labels
Agent
Ignore
Agent
In Progress
Agent
Planned
Agent
Planning
Agent
Ready
Agent
Triage
Compat
Breaking
Kind
Bug
Kind
Documentation
Kind
Enhancement
Kind
Feature
Kind
Security
Kind
Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
lucas/omp-deck#270
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
mise currently reaches a session only through shims. Tool versions resolve correctly per directory, but no environment variable a project declares ever reaches the agent's shell.
The cause is not
process.envas such: the agent's bash never starts from liveprocess.env. Its base environment issettings.getShellConfig().env, a boot-time, filtered snapshot of the process environ that is shared by every session in the deck process:session/bash-runner.tsin the agent SDK: "the child shell starts from the (cached, filtered) spawn env — NOT from live process.env".tools/bash-interactive.tssays the same for PTYs (Bun'sprocess.envwrites never reach the C environ).BashTool.#sessionEnv():PI_SESSION_ID,PI_SESSION_FILE).So
PATHis a single image-level value (Dockerfile, thePATH=/data/.local/share/mise/shims:…line) and cwd is the only per-session input. Everything else about mise had to be designed around that.What it costs today
[env]from a project's config never applies:_.file/_.sourcedotenvs,[env._.python.venv]auto-venv,JAVA_HOME,GOROOT,NODE_OPTIONS, … The agent is instead told to route builds throughmise exec --/mise run(apps/server/src/mise/system-context.ts).PATHentry:node_modules/.bin,cargo installoutput,pipx,go install.mise whichovercommand -vto compensate.process.env(apps/server/src/env-store.ts) and reach in-process JS only — agent shells keep the boot snapshot until restart.Proposal
Bridge the environment through direnv, which the agent SDK already loads per directory, instead of inventing a second mechanism.
The SDK ships a complete per-directory env loader —
exec/direnv.tsplusapplyDirenvPreflightinexec/bash-executor.ts— with walk-up.envrcdiscovery, a watch-file mtime cache, a load-timeout budget clamped by the caller's command timeout,unsethandling via a regex-gatedunset -vprefix, and caller-env-wins merge semantics. It is on by default (bash.direnv = auto) and already feeds the one-shot, ACP and PTY backends.Sketch:
direnvto the image.apps/server/src/mise/provision.ts/materialize.ts) also writes a deck-managed.envrcinto the workspace and marks it allowed.MANAGED_HEADERfirst line, never clobber a file the deck did not write (MiseConfigConflict→ workspace phasefailed), append to.git/info/exclude, remove again when the feature/config is cleared.Chosen over the alternatives because it is the flexible one: any env a project wants — mise
[env], a venv, plain exports, a devenv/nix shell — arrives through one mechanism, rather than only whatmise envcan emit.Why not the obvious alternatives
shellPathsetting. Feasible with no SDK change and keeps today's trust boundary exactly, but the environment is static per workspace: no per-subdirectory env in a monorepo, and stale until re-provision. Env-only, no.envrcescape hatch.ToolCallEventResult.input). Cheapest, but the rewritten command is what the user sees and what the permission gate reasons about, and repeated sourcing accumulatesPATHin the persistent shell.shell.envlayer or a mise preflight upstream in the SDK. The correct endgame and the direnv code is a ready-made template, but it is not a change in this repository.Risks / things to decide
use miseevaluates the config found in cwd, and mise[env]._.sourceruns a script. A Forgejo trigger session runs in a clone of a pull-request branch, so a naive per-cwd load is code execution from an untrusted branch — precisely what the current design rejects (see theMISE_NOT_FOUND_AUTO_INSTALL=0rationale in theDockerfile). The.envrcmust pin which config it loads (e.g. ceiling paths / explicit config filenames) rather than resolving freely, and direnv's allow list must cover only the deck-written file, never one that arrives with a checkout..envrcbecomes live the moment direnv exists in the image, independent of this feature, sincebash.direnvdefaults toauto. That needs a deliberate decision (allow-list scoping, orbash.direnv = offin the deck's session settings with only the managed file re-enabled).apps/server/src/routines/v1-runner.tspassesprocess.env) and the eval kernels keep the shared snapshot.PATHand the shim directory's precedence.Acceptance
[env](including a_.filedotenv and an auto-venv) has those variables present in a plainbashtool call, withoutmise exec.node_modules/.bin) resolves in a plain command..envrcor[env]._.sourcedoes not execute it..envrcfollows the existing managed-file contract: not clobbering a user's file, git-excluded, removed when the config is cleared.docs/configuration.md(Dev tools section) andapps/server/src/mise/system-context.tsare updated — the latter currently states the[env]limitation as a fact to the agent.