Support named local environment overlays selectable per thread / turn / tool call
Summary
Codex currently exposes a single effective shell_environment_policy for a session. That works for projects with one stable local toolchain environment, but it breaks down for workflows that need to switch between multiple local build/runtime environments within the same thread.
A common Windows example is a project that needs:
- one environment for ROCm / Python GPU tooling
- a different environment for CPU / MSVC builds, typically initialized via
vcvars*.bator Developer PowerShell
Today the choices are all awkward:
- put everything into one combined environment override and hope the toolchains coexist
- relaunch Codex with a different profile
- use experimental exec-server-backed environments, which are closer to “multiple execution backends” than “multiple local env overlays”
What seems missing is a first-class notion of named local environment overlays.
Problem
[shell_environment_policy] is a single policy object, not a set of named selectable policies.
That creates friction for projects that need to switch toolchains during the same session, for example:
- building GPU code with a Python / ROCm venv
- then building CPU-native code with MSVC environment variables from
vcvars64.bat - then switching back without losing thread state or changing workflows
Profiles are not a good fit here because they are selected at launch and do not support fluid switching within one active thread.
The experimental environments / exec-server model also feels mismatched for this case:
- it models execution backends, not local shell env presets
- it introduces wrapper/launcher complexity
- it is not an ergonomic replacement for “same machine, same repo, different local env overlays”
Proposal
Add named local environments as a first-class config concept, selectable at thread, turn, and tool-call scope.
Example config shape
[local_environments.rocm]
description = "ROCm Python/toolchain environment"
[local_environments.rocm.shell_environment_policy]
inherit = "all"
exclude = ["PATH"]
set = {
VIRTUAL_ENV = "H:\\dinoml_v2\\.venv\\rocm",
Path = "H:\\dinoml_v2\\.venv\\rocm\\Scripts;..."
}
[local_environments.msvc]
description = "MSVC build environment"
[local_environments.msvc.vcvars]
script = "C:\\Program Files\\Microsoft Visual Studio\\2022\\BuildTools\\VC\\Auxiliary\\Build\\vcvars64.bat"
arch = "amd64"
[local_environments.base]
description = "No extra overlay"
[environment_selection]
default = "rocm"
available = ["rocm", "msvc", "base"]
Selection semantics
Environment selection should follow this precedence:
- explicit tool-call environment
- turn-level override
- thread sticky environment
- config default
- existing current behavior
Tool-call shape
Shell-like tools could optionally accept an environment selector:
{
"command": "cmake --build build-cpu",
"environment_id": "msvc"
}
{
"command": "python -c \"import torch; print(torch.version.hip)\"",
"environment_id": "rocm"
}
Why this is different from exec-server environments
This proposal is not asking for more remote or stdio-backed execution targets.
It is specifically for:
- one Codex session
- one local machine
- one repository / filesystem view
- one model context
- multiple local process environment overlays
That is a different problem from selecting multiple execution backends.
Windows-specific need
This seems especially important on Windows, where build environments are often procedural rather than declarative.
For MSVC, users should not have to manually encode the results of vcvars*.bat into set = { ... }. That is brittle and incomplete because vcvars modifies many environment variables, including:
PATHINCLUDELIBLIBPATH- Windows SDK and toolchain-specific vars
A first-class vcvars source would be much more robust than forcing users to approximate the result manually.
Suggested implementation model
Each named local environment would resolve to an effective environment map for spawned subprocesses.
This could support multiple sources:
- direct
shell_environment_policy - derived environment from a script/command
- Windows-specific
vcvarshelper
Example ergonomic built-in for MSVC
[local_environments.msvc.vcvars]
script = "C:\\Program Files\\Microsoft Visual Studio\\2022\\BuildTools\\VC\\Auxiliary\\Build\\vcvars64.bat"
host = "x64"
target = "x64"
Performance expectations
For script-derived environments, especially vcvars, Codex should ideally:
- resolve the environment once
- snapshot the resulting env map
- cache it by script path + args + cwd + mtime
- merge runtime-owned vars like
CODEX_THREAD_IDafterward
That avoids rerunning vcvars on every command.
Model-visible context
If multiple local environments are available, <environment_context> could expose them compactly, for example:
<environment_context>
<cwd>H:\dinoml_v2</cwd>
<shell>powershell</shell>
<local_environments>
<environment id="rocm" current="true" />
<environment id="msvc" />
<environment id="base" />
</local_environments>
</environment_context>
This gives the model enough context to choose the right environment without large token overhead.
Non-goals
This request is not asking for:
- more remote environment support
- replacement of existing exec-server environments
- project-local machine-specific secrets or auth config
- a second model session per environment
Acceptance criteria
A solution would feel complete if it supports:
- defining multiple named local environments in config
- selecting one as the default
- overriding it per thread
- overriding it per turn
- optionally selecting it per shell/unified-exec tool call
- compact visibility in
<environment_context> - a first-class way to derive Windows/MSVC env from
vcvars - caching of derived env snapshots so repeated switches are cheap
Why this would help
This would make Codex much easier to use in real mixed-toolchain projects without requiring users to:
- relaunch with different profiles
- maintain brittle combined env overrides
- route local shell workflows through remote-style execution backends
It would also align better with how developers actually think about these setups: “use the ROCm env for this command, use the MSVC env for that one”, while staying inside the same conversation and repository context.
4 Comments
I am currently using this in my production workflows. I'm using it via Codex Desktop with a patched build. I just wanted to note that it does work and has eliminated environment related failure modes thus far.
My config:
Additional features on the linked branch not mentioned in the original comment here are
path_prependand a fix for case sensitive PATH vs Path issue on Windows (there are a few existing issues reporting this).path_prependis useful because existingshell_environment_policyrequires explicit full path which is awkward in practice in terms ofconfig.toml.A related use case I ran into while implementing the local-environment MVP is subagents/worktrees.
The original issue is mostly framed around selecting named local environments within one thread, but there is a closely related config-loading problem for spawned child agents:
.codex/config.tomlDINOML_CACHE_DIRIf subagents only inherit the parent’s already-resolved config snapshot, they never see the child worktree’s project-local config.
What seems desirable is:
That matters for the same kind of mixed-toolchain workflows described in this issue. In practice it means things like:
DINOML_CACHE_DIRor local-environment defaults work naturallyThis is a good direction. I would make the selected environment part of the resolved execution context, not just a convenience switch.
The subagent/worktree case is the hard one. Once a child is spawned into a different cwd, there are at least four possible sources of truth:
Before a tool call runs, Codex probably needs to collapse those into one visible record: environment name, config source, script or env digest, working directory, and whether the profile was inherited or reloaded for the child.
That gives you a clean invariant for approvals and resumes: the user approves the command under the exact environment that will execute it. If the selected overlay changes while an approval is pending, or if child config reload changes the effective env, the old approval should be invalidated or rebound through an explicit new prompt.
The UI can still be simple. The deeper thing is that each tool call should carry the resolved environment id/state in the transcript or event stream so resume, subagents, and debugging are not reconstructing it from ambient process state later.
A per-turn selector is useful for ergonomics. A per-tool-call resolved environment record is what makes the feature safe to debug.
Recent upstream work on
environments v2seems to confirm the distinction this issue is trying to make.The newer
environmentId/environments.tomlmodel is getting better at selecting and preserving execution targets:cwdThat all makes sense for execution routing, especially for remote exec-server-backed targets.
But it still does not solve the core request here: defining multiple named local environment overlays on the same machine and switching between them ergonomically within one thread/session.
The gap is that current “environment” support mostly models executor selection, not environment definition.
For local workflows, what is still missing is a first-class way to say:
That matters because
[shell_environment_policy]is too limited for many real setups. In practice, local environments often come from procedural activation flows such as:vcvars*.bat/ Developer PowerShelldirenvvenv/poetry/uvcondanix developThose are not well represented by a single static env policy with
inherit,set, and PATH edits.So I think this issue is still valid even with the recent environment-v2 work:
environments v2improves where execution happensPut differently:
environmentIdcurrently looks closer to an execution target selector than a local environment recipe.If the long-term direction is to converge these concepts, it would help to explicitly support local environment sources such as:
shell_environment_policyvcvarssourcewith caching and named selection semantics as proposed here.