Skip to content

fix(windows): resolve the captured PATH case-insensitively (fixes #232) - #248

Merged
yaojin3616 merged 1 commit into
dataelement:mainfrom
ocensis:fix/windows-case-insensitive-path-lookup
Aug 31, 2026
Merged

fix(windows): resolve the captured PATH case-insensitively (fixes #232)#248
yaojin3616 merged 1 commit into
dataelement:mainfrom
ocensis:fix/windows-case-insensitive-path-lookup

Conversation

@ocensis

@ocensis ocensis commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Fixes #232. Also unblocks #234, which is a downstream symptom of the same empty PATH.

Root cause

Windows stores environment variable names with whatever casing the registry value name carries, and resolveShellEnvironment() returns a plain object keyed by that casing verbatim (parseEnvOutput, src/main/runtime/harness-runtime.ts). On a machine whose PATH value name is stored lowercase, the captured block arrives as path — and the exact-case read at the spawn-options site:

[pathKey]: environment[pathKey] ?? environment.PATH ?? ''

misses it, so the Harness spawns with Path="". The stray lowercase path carried along by the spread does not save it: Node's win32 spawn sorts env keys and case-insensitively dedupes keeping the first, and 'Path' sorts before 'path', so the empty explicit key wins. Measured end-to-end on Windows 11 (26200) / node v22: a child spawned through the current code with a lowercase-path block sees process.env.PATH === "". Every PATH-resolved tool call then fails with ENOENT — including powershell.exe for "Open configuration file" (#234).

process.env never shows this problem because Node makes it case-insensitive on win32 — but that behaviour does not survive a spread. ({...process.env}).Path is undefined on a lowercase-path machine even though process.env.Path is defined. buildProfilePluginCommandEnvironment (src/main/runtime/profile-plugin-command.ts) spreads its environment before reading Path/PATH exact-case, so plugin install/removal/repair subprocesses dropped the user's real PATH on the same machines: only the shim and bundled-node directories survived.

Fix

New helper resolveEnvironmentPath(environment, platform) in harness-runtime.ts:

  • exact-case Path then PATH reads first (preserves existing behaviour for every input the current tests cover),
  • then a case-insensitive scan, win32 only — POSIX keeps the exact read, since path is a genuinely different variable there,

used at both read sites. The write sites are untouched: writing Path/PATH next to the original lowercase key is safe because whichever key survives Node's win32 dedupe carries the same resolved value.

Verification

New regression tests:

  • test/runtime.test.tsbuildHarnessSpawnOptions with a lowercase path block: every key spelling PATH in the returned env must carry the user's value.
  • test/shell-environment-encoding.test.ts — helper-level casing matrix, including the POSIX exact-case guarantee.
  • test/profile-plugin-command.test.tsbuildProfilePluginCommandEnvironment keeps the user PATH from a lowercase block on win32, and keeps path out of PATH on POSIX.

…nt#232)

Windows stores environment variable names with whatever casing the
registry value name carries, and `resolveShellEnvironment()` returns a
plain object keyed by that casing verbatim. A machine whose PATH value
name is stored lowercase hands the launch path the key `path`; the
exact-case read at the spawn-options site then produced an empty PATH,
so the Harness started with no PATH at all and every PATH-resolved tool
call failed with ENOENT.

`process.env` hides this because Node makes it case-insensitive on
win32, but that behaviour does not survive a spread: a copy keeps only
the stored casing. `buildProfilePluginCommandEnvironment` spreads its
environment before reading `Path`/`PATH` exact-case, so plugin install,
removal, and repair subprocesses dropped the user's PATH on the same
machines — only the shim and bundled-node directories survived.

Add `resolveEnvironmentPath()`: exact-case `Path`/`PATH` reads first,
then a case-insensitive scan, win32 only — POSIX keeps the exact read
because `path` is a genuinely different variable there. Use it at both
read sites.
@yaojin3616
yaojin3616 merged commit 1d39aa3 into dataelement:main Aug 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Windows: Harness spawned with empty PATH when env block stores PATH as lowercase 'path' — user tools invisible to tool shells

2 participants