Summary
On Windows, environment-variable names are case-insensitive. #2089 (fixed by #2098) addressed this for a caller-supplied parent_environ. However, rex's internal accumulated environment — ActionManager.environ, i.e. variables a package sets during commands() — still stores and looks up keys verbatim (case-sensitively) on every platform. So a package that sets a variable with one casing and later reads or modifies it with a different casing does not resolve to the value it just set when running on Windows.
This is a separate, pre-existing gap from #2089, and it was intentionally left out of scope by #2098 (which only normalizes caller-supplied parent_environ lookups).
Reproduction
# package commands(), on Windows
def commands():
env.Foo = "child"
env.RESULT = env.FOO # expected "child" on Windows; not what you get
Minimal, self-contained script (plain dict parent, no private symbols — runs as-is on main). The mechanism is platform-independent; the results below are correct on case-sensitive OSes but wrong on Windows, where Foo and FOO are the same variable:
from rez.rex import RexExecutor, Python
def executor(parent):
interp = Python(target_environ={}, passive=True)
return RexExecutor(interpreter=interp, parent_environ=parent, shebang=False)
def _rex():
setenv("Foo", "child")
setenv("RESULT", getenv("FOO")) # on Windows, should resolve to "child"
# 1) Parent has FOO -> the just-set value is shadowed by the stale parent value
ex = executor({"FOO": "parent"})
ex.execute_function(_rex)
print(ex.get_output()["RESULT"]) # -> "parent" (on Windows, expected "child")
# 2) Parent lacks FOO -> RexUndefinedVariableError
ex = executor({})
ex.execute_function(_rex) # raises: Referenced undefined environment variable: FOO
Root cause
ActionManager._key() performs no case-folding, and ActionManager.environ is a plain dict:
setenv() writes self.environ[expanded_key] = ... verbatim, so setenv("Foo", ...) stores {"Foo": ...}.
getenv() does self.environ[expanded_key] if expanded_key in self.environ else self.parent_environ[expanded_key], so getenv("FOO") misses self.environ and falls back to parent_environ (stale value, or RexUndefinedVariableError).
The same exact-casing assumption affects defined()/undefined(), unsetenv(), prependenv()/appendenv(), resetenv(), and the EnvironmentDict variable cache (which can build separate wrappers for env.Foo and env.FOO).
Suggested direction
Make rex's internal env-var handling case-insensitive on Windows — e.g. normalize keys in _key() when platform_.name == "windows", and/or back self.environ with a case-insensitive mapping. This is a broader change than #2098: it touches set/get/unset/pend/reset/defined/undefined and the EnvironmentDict cache, and would need tests across the Python, cmd and PowerShell interpreters — which is exactly why #2098 kept its scope to parent_environ.
Environment
Disclosure: Claude Code (Opus 4.8) used to investigate, reproduce, and draft this report.
Summary
On Windows, environment-variable names are case-insensitive. #2089 (fixed by #2098) addressed this for a caller-supplied
parent_environ. However, rex's internal accumulated environment —ActionManager.environ, i.e. variables a package sets duringcommands()— still stores and looks up keys verbatim (case-sensitively) on every platform. So a package that sets a variable with one casing and later reads or modifies it with a different casing does not resolve to the value it just set when running on Windows.This is a separate, pre-existing gap from #2089, and it was intentionally left out of scope by #2098 (which only normalizes caller-supplied
parent_environlookups).Reproduction
Minimal, self-contained script (plain
dictparent, no private symbols — runs as-is onmain). The mechanism is platform-independent; the results below are correct on case-sensitive OSes but wrong on Windows, whereFooandFOOare the same variable:Root cause
ActionManager._key()performs no case-folding, andActionManager.environis a plaindict:setenv()writesself.environ[expanded_key] = ...verbatim, sosetenv("Foo", ...)stores{"Foo": ...}.getenv()doesself.environ[expanded_key] if expanded_key in self.environ else self.parent_environ[expanded_key], sogetenv("FOO")missesself.environand falls back toparent_environ(stale value, orRexUndefinedVariableError).The same exact-casing assumption affects
defined()/undefined(),unsetenv(),prependenv()/appendenv(),resetenv(), and theEnvironmentDictvariable cache (which can build separate wrappers forenv.Fooandenv.FOO).Suggested direction
Make rex's internal env-var handling case-insensitive on Windows — e.g. normalize keys in
_key()whenplatform_.name == "windows", and/or backself.environwith a case-insensitive mapping. This is a broader change than #2098: it touchesset/get/unset/pend/reset/defined/undefinedand theEnvironmentDictcache, and would need tests across the Python, cmd and PowerShell interpreters — which is exactly why #2098 kept its scope toparent_environ.Environment
main; pre-existing (present before Fix rex parent_environ case sensitivity on Windows #2098)Disclosure: Claude Code (Opus 4.8) used to investigate, reproduce, and draft this report.