Windows standalone update from pwsh inherits PSModulePath into powershell.exe, causing Get-FileHash to fail

Open 💬 23 comments Opened Jun 9, 2026 by BlueOcean223

What happened?

While investigating a Windows standalone update failure, I found that the update action launches Windows PowerShell (powershell.exe) even when Codex itself is started from PowerShell 7 (pwsh). The child powershell.exe process can inherit PowerShell 7 entries in PSModulePath, which breaks module discovery in Windows PowerShell 5.1 and makes Get-FileHash unavailable.

The resulting installer failure looks like this:

Get-FileHash : The term 'Get-FileHash' is not recognized as the name of a cmdlet, function, script file, or operable program.

This does not appear to be caused by irm ... | iex corrupting the installer script text. I verified that the downloaded script text and piped script text are identical and parse successfully.

Environment

OS: Windows 10 22H2, 10.0.19045
Codex: codex-cli 0.138.0
Parent shell: pwsh 7.6.2
Child shell used by standalone update: powershell.exe 5.1.19041.6456
Codex executable: C:\Users\Administrator\AppData\Local\Programs\OpenAI\Codex\bin\codex.exe

Current main still has the Windows standalone update action hard-coded as:

UpdateAction::StandaloneWindows => (
    "powershell",
    &[
        "-ExecutionPolicy",
        "Bypass",
        "-c",
        "$env:CODEX_NON_INTERACTIVE=1; irm https://chatgpt.com/codex/install.ps1 | iex",
    ],
),

Observed on main commit:

08cb633c06a27d25872d0132fbd9c749556d7653

Minimal reproduction

This does not execute the Codex installer. It only simulates the relevant process-launch shape and checks whether Windows PowerShell can resolve and run Get-FileHash.

Run this from pwsh:

node - <<'NODE'
const { spawnSync } = require('child_process');

const psProbe = String.raw`
$ErrorActionPreference = 'Continue'
"PSVersion=$($PSVersionTable.PSVersion) Edition=$($PSVersionTable.PSEdition)"
"PSModulePath=$env:PSModulePath"
$cmd = Get-Command Get-FileHash -ErrorAction SilentlyContinue
"Get-FileHash found=$([bool]$cmd) type=$($cmd.CommandType) source=$($cmd.Source)"
try {
  'Get-FileHash -LiteralPath $PSHOME\\powershell.exe -Algorithm SHA256 | Select-Object -First 1 Algorithm,Hash' | Invoke-Expression | Format-List
  "InvokeExpressionResult=OK"
} catch {
  "InvokeExpressionResult=FAIL"
  "Error=$($_.Exception.Message)"
}
`;

const enc = Buffer.from(psProbe, 'utf16le').toString('base64');
const cleanWindowsPSModulePath = [
  'C:\\Users\\Administrator\\Documents\\WindowsPowerShell\\Modules',
  'C:\\Program Files\\WindowsPowerShell\\Modules',
  'C:\\Windows\\system32\\WindowsPowerShell\\v1.0\\Modules',
].join(';');

const cases = [
  ['A. inherited env, like native child process', { ...process.env }],
  ['B. PSModulePath removed, let Windows PowerShell rebuild', (() => { const e = { ...process.env }; delete e.PSModulePath; delete e.PSMODULEPATH; return e; })()],
  ['C. clean WindowsPowerShell-only PSModulePath', { ...process.env, PSModulePath: cleanWindowsPSModulePath }],
];

for (const [name, env] of cases) {
  const r = spawnSync('powershell.exe', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-EncodedCommand', enc], {
    encoding: 'utf8', timeout: 30000, env
  });
  console.log('\n==== ' + name + ' ====');
  console.log('exit=' + r.status);
  console.log(r.stdout.trim());
  const stderr = r.stderr.replace(/#< CLIXML[\s\S]*/,'<CLIXML progress omitted>').trim();
  if (stderr) console.log('stderr=' + stderr);
}
NODE

Actual output

With the inherited environment, Windows PowerShell 5.1 sees PowerShell 7 module paths first and cannot resolve Get-FileHash:

==== A. inherited env, like native child process ====
exit=0
PSVersion=5.1.19041.6456 Edition=Desktop
PSModulePath=C:\Users\Administrator\Documents\PowerShell\Modules;C:\Program Files\PowerShell\Modules;c:\program files\powershell\7\Modules;C:\Program Files\WindowsPowerShell\Modules;C:\Windows\system32\WindowsPowerShell\v1.0\Modules
Get-FileHash found=False type= source=
InvokeExpressionResult=FAIL
Error=The term 'Get-FileHash' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.

If PSModulePath is removed before spawning powershell.exe, Windows PowerShell rebuilds a WindowsPowerShell-only module path and Get-FileHash works:

==== B. PSModulePath removed, let Windows PowerShell rebuild ====
exit=0
PSVersion=5.1.19041.6456 Edition=Desktop
PSModulePath=C:\Users\Administrator\Documents\WindowsPowerShell\Modules;C:\Program Files\WindowsPowerShell\Modules;C:\Windows\system32\WindowsPowerShell\v1.0\Modules
Get-FileHash found=True type=Function source=Microsoft.PowerShell.Utility

Algorithm : SHA256
Hash      : 9785001B0DCF755EDDB8AF294A373C0B87B2498660F724E76C4D53F9C217C7A3

InvokeExpressionResult=OK

Codex-shaped safe reproduction

This mirrors the update action shape (powershell -ExecutionPolicy Bypass -c "... | iex") without running the installer:

==== codex-shaped command, inherited env ====
Get-FileHash : The term 'Get-FileHash' is not recognized as the name of a cmdlet, function, script file, or operable program.

==== codex-shaped command, PSModulePath removed ====
SHA256    9785001B0DCF755EDDB8AF294A373C0B87B2498660F724E76C4D53F9C217C7A3

Why this does not look like an irm | iex encoding problem

I also checked the real installer script without executing it. The direct Invoke-RestMethod result and the piped text had the same length, same SHA-256 over the text, same Get-FileHash index, and both parsed with zero PowerShell parser errors:

directLength=29883
pipeLength=29883
directSha256=ab7d7864801e62f92aa3abf878e3b0aaa8b9f7de03120bc81b029f67281c1842
pipeSha256=ab7d7864801e62f92aa3abf878e3b0aaa8b9f7de03120bc81b029f67281c1842
directParserErrors=0
pipeParserErrors=0
directGetFileHashIndex=2768
pipeGetFileHashIndex=2768

Expected behavior

The standalone Windows update should run reliably when Codex is started from pwsh, without making Windows PowerShell 5.1 inherit a PowerShell 7 module path that breaks module discovery.

Possible fixes

A few possible directions:

  1. Add -NoProfile to the Windows standalone update invocation.
  2. Remove or normalize PSModulePath before spawning powershell.exe for StandaloneWindows, so Windows PowerShell can rebuild its default WindowsPowerShell module path.
  3. Prefer pwsh when Codex itself is running from pwsh and pwsh is available.
  4. Add a fallback in install.ps1 that computes SHA-256 using .NET APIs if Get-FileHash is unavailable.

The most targeted fix seems to be normalizing/removing PSModulePath for the powershell.exe child process used by the Windows standalone update action.

I am happy to help with a PR if maintainers agree with the preferred fix direction.

View original on GitHub ↗

23 Comments

thomak-dev · 2 months ago

I am having this problem when trying to update codex from PowerShell 7, which is my main shell. The expectation is that codex update works from any shell

rteoo · 1 month ago

I can confirm this is still reproducible with a newer Codex release and the latest PowerShell 7 available in my environment.

Environment

  • Windows: 10.0.26200.8655
  • Parent shell: PowerShell 7.6.3
  • Codex CLI: 0.141.0
  • Target update: 0.142.5

Reproduction

From PowerShell 7.6.3:

codex update

The updater launches Windows PowerShell rather than the current pwsh process:

Updating Codex via `powershell -ExecutionPolicy Bypass -c '$env:CODEX_NON_INTERACTIVE=1; irm https://chatgpt.com/codex/install.ps1 | iex'`...
==> Updating Codex CLI from 0.141.0 to 0.142.5
==> Detected platform: Windows (x64)
==> Resolved version: 0.142.5
==> Downloading Codex CLI
iex : The term 'Get-FileHash' is not recognized as the name of a cmdlet, function, script file, or operable program.

Running Windows PowerShell 5.1 independently with a clean environment resolves Get-FileHash from Microsoft.PowerShell.Utility, which is consistent with the root-cause analysis in this issue: the failure is caused by the PowerShell 7 PSModulePath inherited by the powershell.exe child process, not by Get-FileHash being unavailable on the machine.

Proposed fix

For the StandaloneWindows update action:

  1. Keep powershell.exe as the compatibility baseline because pwsh is not guaranteed to be installed.
  2. Add -NoProfile.
  3. Remove PSModulePath from the child process environment before spawning powershell.exe, allowing Windows PowerShell to reconstruct its native module search path.
  4. Add a Windows regression test that starts the update command with a PowerShell 7-style PSModulePath and verifies that the child can resolve and execute Get-FileHash.

A defensive .NET SHA-256 fallback in install.ps1 could provide an additional safety net, but normalizing the child environment appears to be the smallest fix at the source.

This also appears to be the same failure reported in #30015. I would be happy to prepare the focused change and regression test if a maintainer would like to invite a PR under the repository's contribution policy.

duckyb · 1 month ago

I tested this solution (proposed by OP) and it worked great for me. If you're having issues updating codex cli I recommend trying it. 👍🏻

powershell -NoProfile -ExecutionPolicy Bypass -c '$env:CODEX_NON_INTERACTIVE=1; irm https://chatgpt.com/codex/install.ps1 | iex'

balloon72 · 1 month ago

I reproduced this on current main (5c19155cbd93) on Windows, with PowerShell 7.6.3 spawning Windows PowerShell 5.1.26100.8457.

With the inherited PSModulePath, Get-Command Get-FileHash returned no command. Removing PSModulePath before spawning the same child restored Microsoft.PowerShell.Utility. Both probes used -NoProfile, so adding that flag alone is not sufficient.

The current standalone Windows path in codex-rs/cli/src/main.rs directly spawns the updater command without adjusting its environment. The smallest fix looks like adding .env_remove("PSModulePath") only for UpdateAction::StandaloneWindows, while keeping powershell.exe and every other update path unchanged.

I can keep the patch to that focused change and validate it with the PowerShell probe, just test -p codex-cli, just fix -p codex-cli, and just fmt.

If this matches the intended direction, would a maintainer like me to open an invited PR for this issue?

dzshzx · 1 month ago

Confirmed with a full isolated standalone update, not just a command-resolution probe.

Environment

  • Windows 11 x64: 10.0.26100.8655
  • Parent shell: PowerShell 7.6.3
  • Source Codex: 0.142.5
  • Target Codex: 0.144.1
  • Child updater shell: Windows PowerShell 5.1.26100.8655

I copied the retained 0.142.5 standalone package into a temporary standalone layout, pointed CODEX_HOME and CODEX_INSTALL_DIR at isolated directories, and ran that binary's real codex update path. The production installation was not used by the update.

With the inherited environment, instrumentation in the child showed:

DIAG_PARENT=codex.exe
DIAG_PSHOME=C:\Windows\System32\WindowsPowerShell\v1.0
DIAG_PSMODULEPATH=...;C:\Program Files\PowerShell\7\Modules;...;C:\Windows\System32\WindowsPowerShell\v1.0\Modules

Name    : Microsoft.PowerShell.Utility
Version : 7.0.0.0
Path    : C:\Program Files\PowerShell\7\Modules\Microsoft.PowerShell.Utility\...

The real installer then failed at the archive verification step with:

Get-FileHash : The term 'Get-FileHash' is not recognized

I repeated the same isolated update after removing only PSModulePath from the environment inherited by codex.exe. The Windows PowerShell child reconstructed its native module path:

DIAG_PSMODULEPATH=...\Documents\WindowsPowerShell\Modules;C:\Program Files\WindowsPowerShell\Modules;C:\Windows\System32\WindowsPowerShell\v1.0\Modules

Name    : Microsoft.PowerShell.Utility
Version : 3.1.0.0
Path    : C:\Windows\System32\WindowsPowerShell\v1.0\Modules\Microsoft.PowerShell.Utility\...

The Get-FileHash step passed and Codex 0.144.1 was installed into the isolated target. This confirms that the narrow source fix is to remove PSModulePath only on the StandaloneWindows updater child process before spawning powershell.exe.

PowerShell's documented pwsh -> powershell.exe module-path translation does not apply here because native codex.exe sits between the two shells:

pwsh.exe -> codex.exe -> powershell.exe

Adding -NoProfile alone does not address the inherited process environment. I am preparing the focused .env_remove("PSModulePath") change and a unit-level command construction test. This is also the same user-visible failure reported in #30015.

dzshzx · 1 month ago

The focused implementation is ready for maintainer review:

Scope:

  • move the existing update-process runner out of the 4,000-line cli/src/main.rs into a focused module;
  • remove PSModulePath only from the StandaloneWindows PowerShell child;
  • keep all other update paths unchanged;
  • add a command-construction regression test that asserts the child environment removal.

Validation:

just test -p codex-cli
295 tests run: 295 passed, 0 skipped

just fix -p codex-cli
passed

just fmt
passed

git diff --check
passed

I have not opened a pull request because docs/contributing.md says external PRs require an explicit maintainer invitation and uninvited PRs are closed without review. If a maintainer confirms this direction and invites the PR, the branch is ready to open immediately.

galar71 · 1 month ago

Confirmed independently on the current standalone release; this is still reproducible in Codex CLI 0.144.5.

Environment

  • Windows 11 x64
  • Parent shell: PowerShell 7.6.3
  • Windows PowerShell child: 5.1.26100.8875
  • Standalone Codex update: 0.144.4 -> 0.144.5
  • Authentication: ChatGPT account (no API-key installation path involved)

User-visible reproduction

Running the ordinary command from PowerShell 7:

codex update

downloads the update, then fails during archive verification:

iex : The term 'Get-FileHash' is not recognized as the name of a cmdlet,
function, script file, or operable program.
...
Error: `powershell -ExecutionPolicy Bypass -c '$env:CODEX_NON_INTERACTIVE=1; irm https://chatgpt.com/codex/install.ps1 | iex'` failed with status exit code: 1

Opening Windows PowerShell 5.1 and running the same ordinary codex update completed successfully.

Additional isolation

Both installed shells have a valid Get-FileHash:

  • PowerShell 7 resolves it from Microsoft.PowerShell.Utility 7.0.
  • Windows PowerShell 5.1 resolves it from Microsoft.PowerShell.Utility 3.1.

Profiles and user/machine PSModulePath configuration were checked; there are no custom module-path or autoload changes causing this.

A read-only process-chain probe reproduced the boundary precisely:

pwsh.exe -> powershell.exe
Get-FileHash: Microsoft.PowerShell.Utility

pwsh.exe -> native intermediary -> powershell.exe
Get-FileHash: not found

That matches the production chain:

pwsh.exe -> codex.exe -> powershell.exe

The direct PowerShell 7-to-Windows PowerShell launch performs PowerShell's documented PSModulePath compatibility translation. Inserting a native process preserves the PowerShell 7 process environment and bypasses that handoff.

This confirms the report and the focused fix already prepared in this thread: remove PSModulePath from the StandaloneWindows updater child before spawning powershell.exe. The expected behavior is that the literal codex update command works from PowerShell 7 without profiles, wrappers, alternate commands, or switching shells.

Swatto86 · 1 month ago

Still reproduces on the latest release. codex update from pwsh 7.6.3 failed updating 0.144.3 → 0.145.0 with the same Get-FileHash error.

OS: Windows 11 Pro 26200
Parent shell: pwsh 7.6.3
Child shell (standalone update): powershell.exe 5.1.26100.8894

Confirmed the exact mechanism described here: the inherited PSModulePath puts PowerShell 7 module dirs ahead of the Windows PowerShell ones, so WinPS 5.1 autoloads the PS7 copy of Microsoft.PowerShell.Utility (from ...\powershell\7\Modules\...), which does not surface Get-FileHash under 5.1. Forcing a clean WinPS-only PSModulePath on the child fixes it:

$env:PSModulePath = "C:\Program Files\WindowsPowerShell\Modules;C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules"
powershell -ExecutionPolicy Bypass -NoProfile -c "$env:CODEX_NON_INTERACTIVE=1; irm https://chatgpt.com/codex/install.ps1 | iex"

Note -NoProfile alone (fix #1) is not sufficient — the shadowing comes from the inherited PSModulePath, not the profile. Option #2 (normalize/remove PSModulePath before spawning powershell.exe) or #4 (.NET SHA-256 fallback in install.ps1) are the robust fixes.

Rezetyan · 1 month ago

Confirmed on the latest release: 0.144.6 → 0.145.0, with a parent shell combination not yet reported here — pwsh 7.6.4 MSIX (Microsoft Store install) — child powershell.exe 5.1.26100.8894, Windows 11 Pro 26200. Same Get-FileHash CommandNotFound at the checksum step.

Two findings I haven't seen in this thread yet, plus an explanation of why manually re-running the install command appears to work:

1. The shadowing module is Microsoft.PowerShell.Utility, and its pwsh build declares CompatiblePSEditions = @("Core")

With the inherited PSModulePath, Get-Module Microsoft.PowerShell.Utility -ListAvailable in the child 5.1 resolves to the pwsh copy (path order wins over C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules). Its manifest:

ModuleVersion = "7.0.0.0"
CompatiblePSEditions = @("Core")

Windows PowerShell 5.1 cannot load this Core-only build and — critically — does not fall back to the compatible copy later in PSModulePath, so Get-Command Get-FileHash returns nothing and install.ps1 dies.

Shadowing by this single module is sufficient: staging a directory containing only pwsh's Microsoft.PowerShell.Utility, then launching powershell.exe with PSModulePath = <staged dir>;<system32 WinPS modules>, already reproduces Get-FileHash: not recognized. Both pwsh install flavors ship the shadowing content: MSI (C:\Program Files\PowerShell\7\Modules) and MSIX/Store (C:\Program Files\WindowsApps\Microsoft.PowerShell_7.6.4.0_x64__8wekyb3d8bbwe\Modules).

2. Why re-running the command manually from pwsh "works": pwsh hands direct powershell.exe children a clean PSModulePath — a native middle process defeats that

  • pwsh 7.6.4 → powershell.exe (direct child): the child receives a clean WinPS-default PSModulePath (Documents\WindowsPowerShell\Modules; ...\WindowsPowerShell\Modules; ...\system32\...\v1.0\Modules) and Get-FileHash resolves fine. This is likely why @duckyb's manual re-run of the install command works — it's the direct launch, not the -NoProfile flag (consistent with @balloon72's observation that -NoProfile alone doesn't fix the inherited-env case).
  • pwsh 7.6.4 → node.exe → powershell.exe (native middle process, the exact launch shape of codex.exe): the middle process forwards the raw pwsh environment, the child 5.1 gets the poisoned PSModulePath, Get-FileHash goes missing. Minimal repro:

``powershell
node -e "require('child_process').execFileSync('powershell',['-NoProfile','-Command','Get-Command Get-FileHash'],{stdio:'inherit'})"
``

So the bug is specific to the pwsh 7 → native process → powershell.exe chain — precisely the standalone-update launch shape, and also why codex update from a Windows PowerShell 5.1 window succeeds (clean path end-to-end).

Duplicates

#34030 and #30015 report the same failure signature and can probably be closed as duplicates of this one.

User-side workarounds (until the fix lands — @dzshzx's .env_remove("PSModulePath") branch looks right)

  • Run codex update from a Windows PowerShell 5.1 or cmd window; or
  • Git Bash: env -u PSModulePath codex update
  • pwsh: $bak=$env:PSModulePath; Remove-Item Env:PSModulePath; codex update; $env:PSModulePath=$bak
duckyb · 1 month ago
* This is likely why @duckyb's manual re-run of the install command works — it's the direct launch, not the -NoProfile flag (consistent with @balloon72's observation that -NoProfile alone doesn't fix the inherited-env case).

No, a manual rerun of the original command failed to update codex with the same error in my environment. It only worked for me after I added the -NoProfile flag.

Zburgers · 24 days ago

Still reproducible while updating the native Windows Codex CLI from 0.145.0 to 0.146.0.

Environment:

  • Windows 10 x64
  • Windows Terminal / PowerShell
  • Codex launched through a local harness
  • Native standalone Codex installation

The updater invoked:

powershell -ExecutionPolicy Bypass -c '$env:CODEX_NON_INTERACTIVE=1; irm https://chatgpt.com/codex/install.ps1 | iex'

The install first reported that it could not download or verify the releases.openai.com SHA256SUMS file and retried through GitHub Releases. The fallback then failed with:

Get-FileHash : The term 'Get-FileHash' is not recognized as the name of a cmdlet, function, script file, or operable program.

The harness does not modify the installer script; it only invokes the Codex update flow. This appears consistent with the inherited PowerShell PSModulePath problem described in this issue, and confirms that the failure is still present in the 0.145.0 → 0.146.0 update path.

I have not yet verified the PSModulePath-removal workaround on this machine, so I am not claiming that workaround as confirmed for this reproduction.

kendonB · 22 days ago

Still an issue today

smartnuf · 20 days ago

Confirmed this still affects the native Windows standalone update path on ARM64.

Environment:

  • Windows 11 ARM64
  • parent shell: native ARM64 PowerShell 7 (pwsh)
  • Codex CLI before update: 0.144.1
  • target/update ultimately installed successfully: 0.147.0 ARM64

Running codex update caused Codex to invoke:

powershell -ExecutionPolicy Bypass -c '$env:CODEX_NON_INTERACTIVE=1; irm https://chatgpt.com/codex/install.ps1 | iex'

One attempt failed because Get-FileHash was not recognized.

I then downloaded the official installer script and ran it from native pwsh; it completed successfully and installed the correct 0.147.0-aarch64-pc-windows-msvc release.

This provides an ARM64/current-release confirmation of the problem described here: a native pwsh Codex session is still delegating its standalone update to legacy powershell.exe.

I have not independently repeated the PSModulePath-isolation experiment from this issue, so I am not claiming that irm | iex itself is the cause. The existing PSModulePath diagnosis here is consistent with what I observed.

Using native pwsh for the update path (or otherwise sanitising the Windows PowerShell environment as proposed here) avoids the failure in my installation.

snowyukitty · 19 days ago

Confirmed this is still reproducible on 2026-08-09 when updating the native Windows x64 standalone install from Codex 0.145.0 to 0.147.0.

The parent was pwsh 7.6.4, where Get-FileHash resolved from Microsoft.PowerShell.Utility 7.0. Codex resolved powershell to 64-bit Windows PowerShell 5.1.26100.8972 (Desktop). A harmless temporary-file SHA-256 probe, with paths sanitized, produced:

  • inherited PSModulePath: the child inherited user/PowerShell 7/Windows PowerShell module roots, discovered the 7.0 Core utility manifest before 3.1 Desktop, and Get-FileHash was absent;
  • inherited path plus -NoProfile: identical failure;
  • PSModulePath removed by a native launcher before child creation: Windows PowerShell rebuilt its Windows-PowerShell-only defaults, loaded Utility 3.1, and returned the expected digest;
  • an explicitly clean Windows PowerShell module path: success;
  • direct pwsh: success.

Invoke-RestMethod remained available because the contaminated manifest could still expose its Windows PowerShell assembly command, while the Windows PowerShell Get-FileHash function was shadowed. No persistent environment was changed.

I also stubbed the primary download locally: a successful file write followed by missing Get-FileHash produced the exact “Could not download or verify” fallback warning. That warning comes from a shared download-and-verification catch block, so the original warning alone does not establish a network failure.

Current origin/main (646f7c0a91b8e327d263335da68ae8ef212895ce) still calls Get-FileHash and does not sanitize this child environment; I found no upstream PR. I tested both public candidate directions: child-only PSModulePath isolation passes the codex-cli suite and fixes codex update/TUI “Update now”, but does not change the displayed command; installer-side .NET SHA-256 passed known-digest, mismatch, and stream-disposal probes under contaminated Windows PowerShell 5.1 and pwsh 7.6.4, and covers the installer regardless of launcher. A direct pwsh -> powershell launch normalized the environment and succeeded on this machine, so the displayed command is not universally broken; the Rust-only mitigation simply cannot harden every external launcher.

Maintainer-ready patch

Building on the installer-side direction above, I prepared a focused, one-commit patch with regression coverage: f3596ea5.

It changes only Test-ArchiveDigest to compute SHA-256 through System.Security.Cryptography.SHA256 and a read-only stream, with both resources disposed in finally blocks. Checksum comparison and failure behavior remain mandatory and unchanged. This is preferable to shell selection or path normalization because it:

  • covers codex update, TUI “Update now”, and direct/manual installer invocation;
  • remains compatible with Windows PowerShell 5.1 and PowerShell 7;
  • does not mutate PSModulePath, change the selected shell, or weaken verification.

The new installer test extracts the real function from install.ps1, replaces Get-FileHash with a throwing stub, and verifies the known digest, exact mismatch error, and exclusive reopening of the fixture after hashing. Reverting the production hunk makes this test fail under both Windows PowerShell 5.1 and pwsh 7.6.4; the patch passes both. The full installer discovery suite passes 18/18, Ruff and just fmt pass, and git diff --check is clean. The test is automatically discovered by the existing repo-checks workflow.

The contribution guide asks external contributors not to open unsolicited PRs. If this design is acceptable, would a maintainer please either cherry-pick the commit or explicitly invite a PR? I can open the focused PR immediately and reference both reports without creating a competing unsolicited PR.

Refs #30015.

harshvasudeva · 9 days ago

I encountered the same Windows standalone update failure.

Environment:

  • Codex CLI: 0.147.0 installed now; failure occurred while updating 0.146.0 -> 0.147.0
  • OS: Windows 11 Pro, build 26200, 64-bit
  • Parent shell: PowerShell 7.6.5 (pwsh)
  • Child shell used by the updater: Windows PowerShell 5.1.26100.9168 (powershell.exe)
  • Error: Get-FileHash is not recognized during codex update
  • The installer first fell back from releases.openai.com to GitHub Releases, then failed during SHA-256 verification.

I confirmed that Get-FileHash is available in both PowerShell installations when module paths are normal. Running the installer in a clean Windows PowerShell session with Microsoft.PowerShell.Utility explicitly imported succeeds.

This appears consistent with the inherited PSModulePath issue described here. The explicit workaround I used was:

powershell.exe -NoProfile -ExecutionPolicy Bypass ...

A possible permanent fix would be to normalize/remove PSModulePath before spawning Windows PowerShell, or add a robust hashing fallback in the installer.

snowyukitty · 9 days ago

Update (2026-08-19): this is still reproducible when updating the native Windows standalone install from Codex CLI 0.147.0 to 0.148.0.

The updater again:

  1. invoked powershell.exe from the Codex update path;
  2. reported that it could not download or verify the releases.openai.com codex-package_SHA256SUMS asset and fell back to GitHub Releases;
  3. failed at the same Get-FileHash checksum step.

This is the same failure signature and inherited-PSModulePath mechanism documented above. The initial warning is still consistent with verification failing inside the shared download-and-verification catch path, rather than establishing a separate network failure.

I refreshed the maintainer-ready installer fallback onto current origin/main (3929c99a): 917ada68 (branch compare: main...snowyukitty:fix/windows-installer-dotnet-sha256; since PR creation is restricted on this repository, the change is also viewable as a standing pull request on my fork, kept mergeable against upstream main: snowyukitty/codex#2). It applies cleanly, keeps mandatory SHA-256 verification, and its focused regression test passes under both Windows PowerShell 5.1 and PowerShell 7. Ruff and codex-rs/just fmt also pass.

I have not opened a pull request, in accordance with the invitation-only contribution policy. The branch is ready if a maintainer would like to invite the focused PR.

Cross-reference for triage: #30015 and #34030 report the same Get-FileHash failure signature and are consistent with the root cause documented here. Across the three issues there are currently 30+ 👍 and confirmations spanning Windows 10/11, x64 and ARM64, MSI and MSIX pwsh installs, and every Codex release from 0.138.0 through 0.148.0.

CI evidence (2026-08-20): a public run on a stock GitHub-hosted windows-latest runner now demonstrates the dependency directly: install.ps1 from current main fails the regression test the moment Get-FileHash is unavailable, while the patched version in snowyukitty/codex#2 passes under both Windows PowerShell 5.1 and pwsh. The workflow is independently re-runnable from the ci-validation branch.

norandom · 9 days ago

Another independent confirmation on 2026-08-19 when the native Windows x64 standalone updater targeted Codex CLI 0.148.0.

Failure

The updater reported:

WARNING: Could not download or verify
https://releases.openai.com/codex/releases/0.148.0/codex-package_SHA256SUMS;
retrying from GitHub Releases.

Get-FileHash : The term 'Get-FileHash' is not recognized

and exited from:

powershell -ExecutionPolicy Bypass -c '$env:CODEX_NON_INTERACTIVE=1; irm https://chatgpt.com/codex/install.ps1 | iex'

Environment and controls

  • Windows 11 x64, build 10.0.26200
  • PowerShell 7: 7.6.5
  • Windows PowerShell: 5.1.26100.9168
  • Outside the Codex update process, Windows PowerShell resolves Get-FileHash from Microsoft.PowerShell.Utility both with the user profile and with -NoProfile.
  • A non-installing probe using the updater's exact powershell.exe -ExecutionPolicy Bypass -c launch shape successfully resolved Get-FileHash and produced a 64-character SHA-256 digest.
  • The active profile does not change PSModulePath, disable module autoloading, remove Microsoft.PowerShell.Utility, or override Get-FileHash.

This is consistent with the child-process environment contamination documented in this issue rather than Get-FileHash being absent from the machine. It also confirms that the initial releases.openai.com warning is not sufficient evidence of a network failure: the installer's shared download/verification catch can emit it when checksum verification fails.

snowyukitty · 8 days ago

Two-machine field verification of the patch in snowyukitty/codex#2, plus a detail that explains why manual re-runs often succeed while codex update fails.

On two distinct physical Windows 11 machines (build 26200; MSIX pwsh 7.6.4 and 7.6.5; Windows PowerShell 5.1.26100.9168), using only the updater's spawn shape — no Codex installation touched:

  • pwsh → powershell.exe directly: the child gets a clean, rebuilt PSModulePath and Get-FileHash resolves from 5.1's own Utility 3.1. Recent pwsh hands a directly spawned Windows PowerShell child a clean module path — which is why re-running the install command manually from pwsh can succeed.
  • pwsh → native intermediary → powershell.exe (cmd.exe in the probe, codex.exe in the field): the child inherits pwsh's contaminated PSModulePath verbatim and Get-FileHash does not resolve.
  • In that contaminated child, Test-ArchiveDigest extracted (via the PowerShell AST) from current main's install.ps1 fails with the byte-for-byte field error, while the patched version passes in the same child — same environment, same fixture, same digest.

This matches @galar71's earlier process-chain observation and narrows the trigger precisely: the failure requires a native intermediary between pwsh and Windows PowerShell, which is exactly what codex update is. It also means an installer-side fix protects every spawn shape at once, including already-shipped binaries, since install.ps1 is fetched fresh at update time.

Full transcripts, a CI run on a stock windows-latest runner, and the two-file patch are in snowyukitty/codex#2.

yuu61 · 6 days ago

Confirmed again on 0.148.0 -> 0.149.0 (Windows 11 Pro 26200, pwsh 7 parent, powershell.exe 5.1.26100.9168 child, codex-cli 0.148.0 standalone). The newest confirmations here (@norandom, @snowyukitty, 2026-08-19) target 0.148.0.

The mechanism is already established, so I will not re-report it. The reason I am writing is item 3: both public installer-side branches, including the one in snowyukitty/codex#2, introduce a FIPS regression that neither branch's test can catch. Items 1, 2 and 4 bear on the same merge decision.

1. The six names in FunctionsToExport are the complete list of what stops resolving

The thread establishes the asymmetry (@snowyukitty: "Invoke-RestMethod remained available because the contaminated manifest could still expose its Windows PowerShell assembly command") but not its boundary. The Windows PowerShell 5.1 Microsoft.PowerShell.Utility.psd1 makes it exact:

NestedModules     = "Microsoft.PowerShell.Commands.Utility.dll", "Microsoft.PowerShell.Utility.psm1"
FunctionsToExport = "Get-FileHash", "New-TemporaryFile", "New-Guid", "Format-Hex",
                    "Import-PowerShellDataFile", "ConvertFrom-SddlString"

Everything else Utility exports comes from the binary NestedModule, already loaded through the snap-in before PSModulePath is consulted. Only these six live in the .psm1, so only these six stop resolving after shadowing — all six measured MISSING with the inherited PSModulePath, while Invoke-RestMethod, Write-Host and Expand-Archive resolved. A closed list read off the manifest, not a growing set of observations.

2. Get-FileHash is the only one of those six that install.ps1 calls

@snowyukitty's harness already uses the AST to extract Test-ArchiveDigest. This is the complementary question — which commands does the script lose — so I walked the whole AST, subtracted the script's own function definitions, and ran Get-Command on the remainder under both environments:

ParserErrors=0, 60 distinct commands, 34 script-defined, 26 external -- identical in both

inherited PSModulePath  ->  does not resolve: Get-FileHash
PSModulePath removed    ->  does not resolve: (none)

Get-FileHash occurs exactly once, at install.ps1:217. Extraction uses native tar -xzf (:975, :982), not Expand-Archive, so a shadowed Microsoft.PowerShell.Archive would change nothing. This upgrades the installer-side fix from necessary to sufficient: no second call site is hiding elsewhere in the file.

3. Both public installer-side branches introduce a ::Create() FIPS regression

I read both diffs. Nicolas0315@55babb96 and snowyukitty@917ada68 both hash through [System.IO.File]::OpenRead(...) plus [System.Security.Cryptography.SHA256]::Create().

That is not what Get-FileHash does. From the shipped Microsoft.PowerShell.Utility.psm1 in Windows PowerShell 5.1:

# First see if it has a FIPS algorithm
$hasherType = "System.Security.Cryptography.${Algorithm}CryptoServiceProvider" -as [Type]
if ($hasherType) { $hasher = $hasherType::New() }
else { ... $hasher = $algorithmType::Create() }

The CSP provider is tried first; ::Create() is only the fallback. Measured under Windows PowerShell 5.1:

"System.Security.Cryptography.SHA256CryptoServiceProvider" -as [Type]  ->  present
[System.Security.Cryptography.SHA256]::Create().GetType().FullName     ->  System.Security.Cryptography.SHA256Managed

SHA256Managed is not FIPS-validated and throws when "System cryptography: Use FIPS compliant algorithms" is enforced. So on such a machine either branch replaces a working Get-FileHash path with one that throws — in the exact code path that gates archive trust. Neither regression test can catch it, because the throw only appears under an enforced policy.

Mirroring the module's own probe removes it:

function New-Sha256Hasher {
    # Mirror Get-FileHash: prefer the FIPS-validated CSP, fall back to the managed one.
    $hasherType = "System.Security.Cryptography.SHA256CryptoServiceProvider" -as [Type]
    if ($null -ne $hasherType) { try { return $hasherType::new() } catch { } }
    return [System.Security.Cryptography.SHA256]::Create()
}

Verified in a Windows PowerShell 5.1 child with the inherited PSModulePath: selects SHA256CryptoServiceProvider, digest byte-identical to sha256sum, mismatch still throws. It drops into either branch — @snowyukitty's test_install_ps1.py asserts only that Test-ArchiveDigest never calls Get-FileHash, returns the right digest, throws on mismatch, and releases the handle, so it stays green.

4. A bare [System.IO.File]::OpenRead does not preserve -LiteralPath resolution

.NET resolves a relative path against [Environment]::CurrentDirectory, which does not track Set-Location; Get-FileHash -LiteralPath resolves against the provider location. With the two diverged, [IO.File]::OpenRead ".\we[i]rd.bin" throws while Get-FileHash -LiteralPath returns the digest.

Latent, not live: all three call sites (:129, :140, :150) get absolute paths built at :944/:956/:957 from GetTempPath(), and the regression test passes an absolute fixture path, so it would not catch a change here either. But this is the function gating archive trust, and a later refactor passing a relative path would turn "verify" into "throw, then fall through to the retry path". One line closes it, and keeps -LiteralPath's no-wildcard semantics (verified against a bracketed filename with a decoy present):

$resolvedPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($LiteralPath)

Also measured

  • The failing run downloads twice and adds an api.github.com request. @snowyukitty and @norandom established that the warning comes from the shared catch block. One level further: Test-ArchiveDigest throws three times — :129 (caught, warning at :137), :140 after re-downloading the same asset from GitHub, then :150 uncaught. Between the second and third, :145 makes an extra unauthenticated api.github.com request; on a rate-limited network that 403s and its error replaces the Get-FileHash message. #35342 was closed as a duplicate after attributing this same failure to TLS 1.0/1.1 defaults. One line would have prevented it: Write-WarningStep "... $($Metadata.Url): $($_.Exception.Message); retrying ...".
  • Profile state changes nothing. With the inherited PSModulePath, Get-FileHash is missing both with the profile loaded and with -NoProfile; with it removed it resolves either way. Consistent with @snowyukitty's two-machine result and @galar71's process-chain probe. That does not contradict @duckyb's observation on their machine, but -NoProfile should not be relied on as the fix.
  • For @dzshzx's branch: std::process::Command::env_remove is case-insensitive on Windows, so one call also covers a PSMODULEPATH spelling. .env("PSModulePath", "") is not a substitute — an empty value is inherited as an empty search path, and Get-Module -ListAvailable Microsoft.PowerShell.Archive then finds nothing.
  • Repairing $env:PSModulePath inside the already-running child also works, contrary to intuition: the bound 7.0.0.0 manifest does not export the name, so the lookup re-triggers auto-loading and imports 3.1.0.0 over it. Not my recommendation — it mutates the user's environment — but not impossible either.

Which fix to land first, and why

  1. The installer-side fix first. install.ps1 is fetched fresh at update time, so a redeploy unblocks every already-shipped binary — including the users here who cannot update in order to get the fix. Please fold items 3 and 4 into whichever branch is picked up; as written, both carry the ::Create() FIPS regression.
  2. .env_remove("PSModulePath") second (@dzshzx's branch), to cover future spawns. It changes neither command_args() nor command_str(), so standalone_update_commands_rerun_latest_installer (update_action.rs:163) and the standalone-Windows history-cell snapshot stay green.
  3. The warning-message line whenever the file is next touched.

Happy to provide raw probe output, the AST scan, or the full three-throw trace for any item.

ejsmith · 6 days ago

This is absolute insanity that something as basic as updating the CLI tool on Windows has been broken for so long from an AI company makes it even more insane.

sumanin5 · 4 days ago

Reproduced on a newer Windows setup:

  • OS/platform: Windows x64
  • Parent shell: PowerShell 7.6.5 (pwsh)
  • Current Codex CLI: 0.144.1
  • Target version reported by codex update: 0.149.0
  • Child shell used by the updater: Windows PowerShell (powershell.exe)

Windows PowerShell can resolve the command normally:

powershell.exe -NoProfile -Command "Import-Module Microsoft.PowerShell.Utility; Get-Command Get-FileHash"

This returns Get-FileHash as a public function from Microsoft.PowerShell.Utility 3.1.0.0.

However, running:

codex update

still fails after the installer prints:

Could not download or verify
https://releases.openai.com/codex/releases/0.149.0/codex-package_SHA256SUMS;
retrying from GitHub Releases.

The fatal error remains:

Get-FileHash: The term 'Get-FileHash' is not recognized as the name of a cmdlet,
function, script file, or operable program.

I also created the Windows PowerShell current-user profile, explicitly imported Microsoft.PowerShell.Utility, and verified that it is loaded in a normal powershell.exe -Command session. codex update still fails in the same way.

This confirms the issue is still reproducible in the 0.144.1 → 0.149.0 standalone update path and is not caused by Get-FileHash being absent from the machine.

snowyukitty · 2 days ago

Fresh Windows field confirmation: the official in-product update resolving Codex CLI 0.149.1 still failed in install.ps1 because Get-FileHash was not recognized after the download fell back to GitHub Releases. Clean Windows PowerShell 5.1 and pwsh 7.6.5 sessions on the same host can resolve the cmdlet; the failing powershell.exe child inherited a module-search environment where it could not. This keeps the original report reproducible through 0.149.1.

I followed up on @yuu61's review with a fresh, non-force-pushed v2 branch based on upstream 9be8d6e1c3dbb145d2d7ac3ba46729340e6d8d40:

The v2 patch:

  • removes the installer checksum's dependency on PowerShell module lookup;
  • prefers SHA256CryptoServiceProvider when it is available and constructible, preserving the FIPS-compatible concern raised in review, with SHA256.Create() as the fallback;
  • resolves paths through GetUnresolvedProviderPathFromPSPath before File.OpenRead, preserving PowerShell provider/literal relative-path semantics;
  • adds a regression test that stubs Get-FileHash to throw and runs under both Windows PowerShell 5.1 and pwsh 7.6.5. It covers an absolute path, a relative path containing brackets while PowerShell location differs from [Environment]::CurrentDirectory, a digest mismatch, CSP preference, and stream disposal.

Focused validation passed under both shells, as did Ruff lint/format and git diff --check. I also attempted the repository-wide just fmt; its unrelated Bazel/Starlark step could not launch buildifier, and Windows Application Control blocked uv for the Python formatter steps. It left no changes outside the two installer files.

The current contributing policy says external code contributions and pull requests are not accepted, so I am not opening an upstream PR. The exact branch and commit are provided as implementation and regression-test evidence for maintainers.

nos1609 · 4 hours ago

Positive field result on 2026-08-28, but not yet evidence that the general bug is fixed.

Environment:

  • Windows 11 Pro, build 28000, ARM64
  • Parent shell used for the successful update: PowerShell
  • Installed result: native standalone codex-cli 0.150.1
  • The installed codex.exe creation time confirms that this update completed on 2026-08-28
  • The source version was not captured

For the first time on this machine, the normal Codex update completed from PowerShell without opening a separate cmd.exe window as a workaround.

However, a safe post-update process-chain probe still reproduces the root mechanism described in this issue:

  • direct pwsh 7.6.5 -> powershell.exe 5.1: Get-FileHash resolves from Microsoft.PowerShell.Utility 3.1.0
  • pwsh 7.6.5 -> native intermediary -> powershell.exe 5.1: Get-FileHash does not resolve

The currently served install.ps1 still calls Get-FileHash, and the 0.150.1 standalone update action still launches powershell.exe without removing or normalizing PSModulePath.

Therefore this is one successful field update, not a request to close the issue. The successful run may have used a clean process environment or a different launch route. A controlled update from another old standalone version is still needed before declaring the bug fixed.