TypeScript SDK drops the Windows PATH when the env has multiple PATH casings
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 125k
- Forks
- 19.4k
- PR merge metrics
- PR metrics pending
Description
Summary
On Windows, the TypeScript SDK can drop the caller's entire PATH when the supplied environment contains more than one casing of the variable, spawning the Codex CLI with only the bundled vendor directory on PATH.
Offending code
https://github.com/openai/codex/blob/main/sdk/typescript/src/exec.ts#L476-L483
function pathEnvKey(env: Record<string, string>, platform: NodeJS.Platform): string {
if (platform !== "win32") {
return "PATH";
}
const matchingKeys = Object.keys(env).filter((key) => key.toLowerCase() === "path");
return matchingKeys.includes("Path") ? "Path" : (matchingKeys.at(-1) ?? "PATH");
}
prependPathDirs then collapses every other casing away:
https://github.com/openai/codex/blob/main/sdk/typescript/src/exec.ts#L457-L474
const pathKey = pathEnvKey(env, platform);
if (platform === "win32") {
for (const key of Object.keys(env)) {
if (key.toLowerCase() === "path" && key !== pathKey) {
delete env[key];
}
}
}
const existingEntries = (env[pathKey] ?? "")
.split(path.delimiter)
.filter((entry) => entry.length > 0 && !pathDirs.includes(entry));
env[pathKey] = [...pathDirs, ...existingEntries].join(path.delimiter);
Why it is wrong
pathEnvKey chooses the surviving key by spelling, not by which key actually holds a value:
- It hard-codes a preference for the exact spelling
"Path", even when that entry is empty and a different casing holds the real search path. - Otherwise it falls back to
matchingKeys.at(-1), i.e. whichever casing happens to be last in JS object insertion order — an ordering that carries no meaning on Windows, where the environment block is case-insensitive.
prependPathDirs then deletes every other casing. Because the key that was kept may be the empty one, the populated value is destroyed rather than merged. The two functions disagree: one picks a key by spelling, the other discards the data under all the rest.
Mixed casings arise routinely on Windows — spreading process.env into a literal, merging config objects from several sources, or reading env dictionaries produced by other tooling. Users hit this through the documented env option on Codex/CodexExec.
What the user sees
The Codex CLI is spawned with a PATH containing only the bundled codex-path directory. Everything the CLI shells out to — git, node, bash, system utilities, and any configured MCP server launched by command name — fails to resolve, typically surfacing as ENOENT or "command not found" errors from inside a Codex turn. The failure is silent at the SDK layer and depends on key insertion order, so it reproduces inconsistently across callers.
Steps to reproduce
import { prependPathDirs } from "@openai/codex-sdk/dist/exec.js";
import path from "node:path";
const env = { PATH: `C:\\Windows${path.delimiter}C:\\Tools`, Path: "" };
prependPathDirs(env, ["C:\\vendor"], "win32");
console.log(env);
Observed:
{ Path: 'C:\\vendor' }
C:\Windows;C:\Tools is gone.
Expected:
{ Path: 'C:\\vendor;C:\\Windows;C:\\Tools' }
The same loss occurs for { PATH: <real value>, path: "" }, where at(-1) selects the empty path. Reversing the insertion order of the two keys makes the value survive, which confirms the outcome is decided by object key order rather than by the environment's meaning.
Expected behaviour
- Prefer a
PATHspelling that actually carries a value over one that is empty. - Never discard a populated search path while collapsing duplicate casings.
- Keep the existing behaviour when several casings are populated (the
"Path"spelling continues to win), so no currently passing case changes.
Related
sdk/python/src/openai_codex/client.py (_path_env_key) is a direct port of the same logic and has the same defect. I have kept this report and the accompanying fix scoped to the TypeScript SDK; the Python variant is worth a separate issue.
Introduced in #23786.
I have a branch with a regression test and a fix, and will open a PR referencing this issue.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in sdk/typescript/src/exec.ts, reading pathEnvKey and prependPathDirs together, then run the Windows reproduction from the issue with differently ordered PATH casings. Done means preserving populated PATH entries while prepending the vendor directory and adding a regression test; the report says a branch with this fix already exists.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- node.js, typescript
- Domain
- cli, tooling
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- Half a day
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 35/100