Skip to content

rex: ActionManager.environ is case-sensitive on Windows (setenv/getenv with mixed casing don't resolve) #2164

Description

@thc1006

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugos:windowsWindows-specificshellShell related issues

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions