Support named local environment overlays selectable per thread / turn / tool call

Open 💬 4 comments Opened Jun 10, 2026 by hlky

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*.bat or 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:

  1. explicit tool-call environment
  2. turn-level override
  3. thread sticky environment
  4. config default
  5. 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:

  • PATH
  • INCLUDE
  • LIB
  • LIBPATH
  • 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 vcvars helper

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_ID afterward

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.

View original on GitHub ↗

4 Comments

hlky · 2 months ago

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:


[local_environments.msvc]
description = "MSVC x64 developer shell"

[local_environments.msvc.script]
script = "C:\\Program Files\\Microsoft Visual Studio\\2022\\Professional\\VC\\Auxiliary\\Build\\vcvars64.bat"
shell = "cmd"
args = []

[local_environments.rocm]
description = "ROCm venv"

[local_environments.rocm.shell_environment_policy]
inherit = "all"
set = { VIRTUAL_ENV = "H:\\dinoml_v2\\.venv\\rocm" }
path_prepend = ["H:\\dinoml_v2\\.venv\\rocm\\Scripts"]

Additional features on the linked branch not mentioned in the original comment here are path_prepend and a fix for case sensitive PATH vs Path issue on Windows (there are a few existing issues reporting this). path_prepend is useful because existing shell_environment_policy requires explicit full path which is awkward in practice in terms of config.toml.

hlky · 2 months ago

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:

  • parent thread may be in one worktree
  • spawned subagent may run in a different worktree / cwd
  • that child worktree may have its own .codex/config.toml
  • the child may need different local env defaults or other project-local settings such as DINOML_CACHE_DIR

If subagents only inherit the parent’s already-resolved config snapshot, they never see the child worktree’s project-local config.

What seems desirable is:

  • keep local environment selection as a session/thread/turn concern
  • but when a child agent is spawned into a different cwd/worktree, reload config for that child cwd
  • preserve runtime/session state that should still be inherited
  • allow the child worktree’s project-local local-environment defaults and related config to apply there

That matters for the same kind of mixed-toolchain workflows described in this issue. In practice it means things like:

  • root thread uses one local environment
  • subagent spawned into another worktree picks up that worktree’s own config
  • per-worktree settings like DINOML_CACHE_DIR or local-environment defaults work naturally
  • no need to relaunch or split the conversation
rpelevin · 2 months ago

This 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:

  • parent thread config
  • child worktree config
  • selected local environment
  • the command's actual shell environment

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.

hlky · 2 months ago

Recent upstream work on environments v2 seems to confirm the distinction this issue is trying to make.

The newer environmentId / environments.toml model is getting better at selecting and preserving execution targets:

  • selected environment identity
  • native cwd
  • native shell
  • routing process/filesystem work through the selected target
  • keeping resolved environment handles stable across thread/turn/session state

That 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:

  • derive this local environment from a script / activation command / helper
  • cache the resulting env snapshot
  • reuse it by name
  • select it at thread / turn / tool-call scope

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 PowerShell
  • direnv
  • venv / poetry / uv
  • conda
  • nix develop
  • repo-specific setup scripts

Those 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 v2 improves where execution happens
  • this issue is about how the execution environment itself is defined and selected

Put differently: environmentId currently 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:

  • static shell_environment_policy
  • script/command-derived env snapshots
  • a first-class Windows vcvars source

with caching and named selection semantics as proposed here.