Windows standalone update from pwsh inherits PSModulePath into powershell.exe, causing Get-FileHash to fail
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:
- Add
-NoProfileto the Windows standalone update invocation. - Remove or normalize
PSModulePathbefore spawningpowershell.exeforStandaloneWindows, so Windows PowerShell can rebuild its default WindowsPowerShell module path. - Prefer
pwshwhen Codex itself is running frompwshandpwshis available. - Add a fallback in
install.ps1that computes SHA-256 using .NET APIs ifGet-FileHashis 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.
23 Comments
I am having this problem when trying to update codex from PowerShell 7, which is my main shell. The expectation is that
codex updateworks from any shellI can confirm this is still reproducible with a newer Codex release and the latest PowerShell 7 available in my environment.
Environment
10.0.26200.86557.6.30.141.00.142.5Reproduction
From PowerShell 7.6.3:
The updater launches Windows PowerShell rather than the current
pwshprocess:Running Windows PowerShell 5.1 independently with a clean environment resolves
Get-FileHashfromMicrosoft.PowerShell.Utility, which is consistent with the root-cause analysis in this issue: the failure is caused by the PowerShell 7PSModulePathinherited by thepowershell.exechild process, not byGet-FileHashbeing unavailable on the machine.Proposed fix
For the
StandaloneWindowsupdate action:powershell.exeas the compatibility baseline becausepwshis not guaranteed to be installed.-NoProfile.PSModulePathfrom the child process environment before spawningpowershell.exe, allowing Windows PowerShell to reconstruct its native module search path.PSModulePathand verifies that the child can resolve and executeGet-FileHash.A defensive .NET SHA-256 fallback in
install.ps1could 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.
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'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-FileHashreturned no command. RemovingPSModulePathbefore spawning the same child restoredMicrosoft.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.rsdirectly spawns the updater command without adjusting its environment. The smallest fix looks like adding.env_remove("PSModulePath")only forUpdateAction::StandaloneWindows, while keepingpowershell.exeand 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, andjust fmt.If this matches the intended direction, would a maintainer like me to open an invited PR for this issue?
Confirmed with a full isolated standalone update, not just a command-resolution probe.
Environment
10.0.26100.86557.6.30.142.50.144.15.1.26100.8655I copied the retained 0.142.5 standalone package into a temporary standalone layout, pointed
CODEX_HOMEandCODEX_INSTALL_DIRat isolated directories, and ran that binary's realcodex updatepath. The production installation was not used by the update.With the inherited environment, instrumentation in the child showed:
The real installer then failed at the archive verification step with:
I repeated the same isolated update after removing only
PSModulePathfrom the environment inherited bycodex.exe. The Windows PowerShell child reconstructed its native module path:The
Get-FileHashstep passed and Codex 0.144.1 was installed into the isolated target. This confirms that the narrow source fix is to removePSModulePathonly on theStandaloneWindowsupdater child process before spawningpowershell.exe.PowerShell's documented
pwsh -> powershell.exemodule-path translation does not apply here because nativecodex.exesits between the two shells:Adding
-NoProfilealone 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.The focused implementation is ready for maintainer review:
010e24d3fc05b85ff039bbf82bcd41c5c81e1c5cdzshzx:codex/fix-windows-update-psmodulepathScope:
cli/src/main.rsinto a focused module;PSModulePathonly from theStandaloneWindowsPowerShell child;Validation:
I have not opened a pull request because
docs/contributing.mdsays 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.Confirmed independently on the current standalone release; this is still reproducible in Codex CLI 0.144.5.
Environment
7.6.35.1.26100.88750.144.4 -> 0.144.5User-visible reproduction
Running the ordinary command from PowerShell 7:
downloads the update, then fails during archive verification:
Opening Windows PowerShell 5.1 and running the same ordinary
codex updatecompleted successfully.Additional isolation
Both installed shells have a valid
Get-FileHash:Microsoft.PowerShell.Utility 7.0.Microsoft.PowerShell.Utility 3.1.Profiles and user/machine
PSModulePathconfiguration were checked; there are no custom module-path or autoload changes causing this.A read-only process-chain probe reproduced the boundary precisely:
That matches the production chain:
The direct PowerShell 7-to-Windows PowerShell launch performs PowerShell's documented
PSModulePathcompatibility 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
PSModulePathfrom theStandaloneWindowsupdater child before spawningpowershell.exe. The expected behavior is that the literalcodex updatecommand works from PowerShell 7 without profiles, wrappers, alternate commands, or switching shells.Still reproduces on the latest release.
codex updatefrompwsh 7.6.3failed updating 0.144.3 → 0.145.0 with the sameGet-FileHasherror.Confirmed the exact mechanism described here: the inherited
PSModulePathputs PowerShell 7 module dirs ahead of the Windows PowerShell ones, so WinPS 5.1 autoloads the PS7 copy ofMicrosoft.PowerShell.Utility(from...\powershell\7\Modules\...), which does not surfaceGet-FileHashunder 5.1. Forcing a clean WinPS-onlyPSModulePathon the child fixes it:Note
-NoProfilealone (fix #1) is not sufficient — the shadowing comes from the inheritedPSModulePath, not the profile. Option #2 (normalize/removePSModulePathbefore spawningpowershell.exe) or #4 (.NET SHA-256 fallback ininstall.ps1) are the robust fixes.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) — childpowershell.exe5.1.26100.8894, Windows 11 Pro 26200. SameGet-FileHashCommandNotFound 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 declaresCompatiblePSEditions = @("Core")With the inherited
PSModulePath,Get-Module Microsoft.PowerShell.Utility -ListAvailablein the child 5.1 resolves to the pwsh copy (path order wins overC:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules). Its manifest:Windows PowerShell 5.1 cannot load this Core-only build and — critically — does not fall back to the compatible copy later in
PSModulePath, soGet-Command Get-FileHashreturns nothing andinstall.ps1dies.Shadowing by this single module is sufficient: staging a directory containing only pwsh's
Microsoft.PowerShell.Utility, then launchingpowershell.exewithPSModulePath = <staged dir>;<system32 WinPS modules>, already reproducesGet-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.exechildren a clean PSModulePath — a native middle process defeats thatpwsh 7.6.4 → powershell.exe(direct child): the child receives a clean WinPS-defaultPSModulePath(Documents\WindowsPowerShell\Modules; ...\WindowsPowerShell\Modules; ...\system32\...\v1.0\Modules) andGet-FileHashresolves fine. This is likely why @duckyb's manual re-run of the install command works — it's the direct launch, not the-NoProfileflag (consistent with @balloon72's observation that-NoProfilealone doesn't fix the inherited-env case).pwsh 7.6.4 → node.exe → powershell.exe(native middle process, the exact launch shape ofcodex.exe): the middle process forwards the raw pwsh environment, the child 5.1 gets the poisonedPSModulePath,Get-FileHashgoes 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 updatefrom 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)codex updatefrom a Windows PowerShell 5.1 or cmd window; orenv -u PSModulePath codex update$bak=$env:PSModulePath; Remove-Item Env:PSModulePath; codex update; $env:PSModulePath=$bakNo, 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
-NoProfileflag.Still reproducible while updating the native Windows Codex CLI from
0.145.0to0.146.0.Environment:
The updater invoked:
The install first reported that it could not download or verify the
releases.openai.comSHA256SUMSfile and retried through GitHub Releases. The fallback then failed with:The harness does not modify the installer script; it only invokes the Codex update flow. This appears consistent with the inherited PowerShell
PSModulePathproblem described in this issue, and confirms that the failure is still present in the0.145.0→0.146.0update 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.Still an issue today
Confirmed this still affects the native Windows standalone update path on ARM64.
Environment:
pwsh)Running
codex updatecaused Codex to invoke:One attempt failed because
Get-FileHashwas not recognized.I then downloaded the official installer script and ran it from native
pwsh; it completed successfully and installed the correct0.147.0-aarch64-pc-windows-msvcrelease.This provides an ARM64/current-release confirmation of the problem described here: a native
pwshCodex session is still delegating its standalone update to legacypowershell.exe.I have not independently repeated the PSModulePath-isolation experiment from this issue, so I am not claiming that
irm | iexitself is the cause. The existing PSModulePath diagnosis here is consistent with what I observed.Using native
pwshfor the update path (or otherwise sanitising the Windows PowerShell environment as proposed here) avoids the failure in my installation.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
pwsh7.6.4, whereGet-FileHashresolved fromMicrosoft.PowerShell.Utility7.0. Codex resolvedpowershellto 64-bit Windows PowerShell 5.1.26100.8972 (Desktop). A harmless temporary-file SHA-256 probe, with paths sanitized, produced:PSModulePath: the child inherited user/PowerShell 7/Windows PowerShell module roots, discovered the 7.0 Core utility manifest before 3.1 Desktop, andGet-FileHashwas absent;-NoProfile: identical failure;PSModulePathremoved by a native launcher before child creation: Windows PowerShell rebuilt its Windows-PowerShell-only defaults, loaded Utility 3.1, and returned the expected digest;pwsh: success.Invoke-RestMethodremained available because the contaminated manifest could still expose its Windows PowerShell assembly command, while the Windows PowerShellGet-FileHashfunction was shadowed. No persistent environment was changed.I also stubbed the primary download locally: a successful file write followed by missing
Get-FileHashproduced 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 callsGet-FileHashand does not sanitize this child environment; I found no upstream PR. I tested both public candidate directions: child-onlyPSModulePathisolation passes thecodex-clisuite and fixescodex 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 andpwsh7.6.4, and covers the installer regardless of launcher. A directpwsh -> powershelllaunch 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-ArchiveDigestto compute SHA-256 throughSystem.Security.Cryptography.SHA256and a read-only stream, with both resources disposed infinallyblocks. Checksum comparison and failure behavior remain mandatory and unchanged. This is preferable to shell selection or path normalization because it:codex update, TUI “Update now”, and direct/manual installer invocation;PSModulePath, change the selected shell, or weaken verification.The new installer test extracts the real function from
install.ps1, replacesGet-FileHashwith 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 andpwsh7.6.4; the patch passes both. The full installer discovery suite passes 18/18, Ruff andjust fmtpass, andgit diff --checkis clean. The test is automatically discovered by the existingrepo-checksworkflow.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.
I encountered the same Windows standalone update failure.
Environment:
pwsh)powershell.exe)Get-FileHashis not recognized duringcodex updateI confirmed that
Get-FileHashis 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
PSModulePathissue described here. The explicit workaround I used was:powershell.exe -NoProfile -ExecutionPolicy Bypass ...A possible permanent fix would be to normalize/remove
PSModulePathbefore spawning Windows PowerShell, or add a robust hashing fallback in the installer.Update (2026-08-19): this is still reproducible when updating the native Windows standalone install from Codex CLI
0.147.0to0.148.0.The updater again:
powershell.exefrom the Codex update path;releases.openai.comcodex-package_SHA256SUMSasset and fell back to GitHub Releases;Get-FileHashchecksum step.This is the same failure signature and inherited-
PSModulePathmechanism 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 andcodex-rs/just fmtalso 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-FileHashfailure 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-latestrunner now demonstrates the dependency directly:install.ps1from currentmainfails the regression test the momentGet-FileHashis 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 theci-validationbranch.Another independent confirmation on 2026-08-19 when the native Windows x64 standalone updater targeted Codex CLI
0.148.0.Failure
The updater reported:
and exited from:
Environment and controls
10.0.262007.6.55.1.26100.9168Get-FileHashfromMicrosoft.PowerShell.Utilityboth with the user profile and with-NoProfile.powershell.exe -ExecutionPolicy Bypass -claunch shape successfully resolvedGet-FileHashand produced a 64-character SHA-256 digest.PSModulePath, disable module autoloading, removeMicrosoft.PowerShell.Utility, or overrideGet-FileHash.This is consistent with the child-process environment contamination documented in this issue rather than
Get-FileHashbeing 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.Two-machine field verification of the patch in snowyukitty/codex#2, plus a detail that explains why manual re-runs often succeed while
codex updatefails.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:
powershell.exedirectly: the child gets a clean, rebuiltPSModulePathandGet-FileHashresolves 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.powershell.exe(cmd.exe in the probe,codex.exein the field): the child inherits pwsh's contaminatedPSModulePathverbatim andGet-FileHashdoes not resolve.Test-ArchiveDigestextracted (via the PowerShell AST) from currentmain'sinstall.ps1fails 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 updateis. It also means an installer-side fix protects every spawn shape at once, including already-shipped binaries, sinceinstall.ps1is fetched fresh at update time.Full transcripts, a CI run on a stock
windows-latestrunner, and the two-file patch are in snowyukitty/codex#2.Confirmed again on 0.148.0 -> 0.149.0 (Windows 11 Pro 26200, pwsh 7 parent,
powershell.exe5.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
FunctionsToExportare the complete list of what stops resolvingThe thread establishes the asymmetry (@snowyukitty: "
Invoke-RestMethodremained available because the contaminated manifest could still expose its Windows PowerShell assembly command") but not its boundary. The Windows PowerShell 5.1Microsoft.PowerShell.Utility.psd1makes it exact:Everything else Utility exports comes from the binary
NestedModule, already loaded through the snap-in beforePSModulePathis consulted. Only these six live in the.psm1, so only these six stop resolving after shadowing — all six measured MISSING with the inheritedPSModulePath, whileInvoke-RestMethod,Write-HostandExpand-Archiveresolved. A closed list read off the manifest, not a growing set of observations.2.
Get-FileHashis the only one of those six thatinstall.ps1calls@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 ranGet-Commandon the remainder under both environments:Get-FileHashoccurs exactly once, atinstall.ps1:217. Extraction uses nativetar -xzf(:975,:982), notExpand-Archive, so a shadowedMicrosoft.PowerShell.Archivewould 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 regressionI 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-FileHashdoes. From the shippedMicrosoft.PowerShell.Utility.psm1in Windows PowerShell 5.1:The CSP provider is tried first;
::Create()is only the fallback. Measured under Windows PowerShell 5.1:SHA256Managedis not FIPS-validated and throws when "System cryptography: Use FIPS compliant algorithms" is enforced. So on such a machine either branch replaces a workingGet-FileHashpath 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:
Verified in a Windows PowerShell 5.1 child with the inherited
PSModulePath: selectsSHA256CryptoServiceProvider, digest byte-identical tosha256sum, mismatch still throws. It drops into either branch — @snowyukitty'stest_install_ps1.pyasserts only thatTest-ArchiveDigestnever callsGet-FileHash, returns the right digest, throws on mismatch, and releases the handle, so it stays green.4. A bare
[System.IO.File]::OpenReaddoes not preserve-LiteralPathresolution.NET resolves a relative path against
[Environment]::CurrentDirectory, which does not trackSet-Location;Get-FileHash -LiteralPathresolves against the provider location. With the two diverged,[IO.File]::OpenRead ".\we[i]rd.bin"throws whileGet-FileHash -LiteralPathreturns the digest.Latent, not live: all three call sites (
:129,:140,:150) get absolute paths built at:944/:956/:957fromGetTempPath(), 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):Also measured
api.github.comrequest. @snowyukitty and @norandom established that the warning comes from the shared catch block. One level further:Test-ArchiveDigestthrows three times —:129(caught, warning at:137),:140after re-downloading the same asset from GitHub, then:150uncaught. Between the second and third,:145makes an extra unauthenticatedapi.github.comrequest; on a rate-limited network that 403s and its error replaces theGet-FileHashmessage. #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 ...".PSModulePath,Get-FileHashis 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-NoProfileshould not be relied on as the fix.std::process::Command::env_removeis case-insensitive on Windows, so one call also covers aPSMODULEPATHspelling..env("PSModulePath", "")is not a substitute — an empty value is inherited as an empty search path, andGet-Module -ListAvailable Microsoft.PowerShell.Archivethen finds nothing.$env:PSModulePathinside 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
install.ps1is 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..env_remove("PSModulePath")second (@dzshzx's branch), to cover future spawns. It changes neithercommand_args()norcommand_str(), sostandalone_update_commands_rerun_latest_installer(update_action.rs:163) and the standalone-Windows history-cell snapshot stay green.Happy to provide raw probe output, the AST scan, or the full three-throw trace for any item.
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.
Reproduced on a newer Windows setup:
pwsh)codex update: 0.149.0powershell.exe)Windows PowerShell can resolve the command normally:
This returns
Get-FileHashas a public function fromMicrosoft.PowerShell.Utility3.1.0.0.However, running:
still fails after the installer prints:
The fatal error remains:
I also created the Windows PowerShell current-user profile, explicitly imported
Microsoft.PowerShell.Utility, and verified that it is loaded in a normalpowershell.exe -Commandsession.codex updatestill 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-FileHashbeing absent from the machine.Fresh Windows field confirmation: the official in-product update resolving Codex CLI
0.149.1still failed ininstall.ps1becauseGet-FileHashwas 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 failingpowershell.exechild 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:
SHA256CryptoServiceProviderwhen it is available and constructible, preserving the FIPS-compatible concern raised in review, withSHA256.Create()as the fallback;GetUnresolvedProviderPathFromPSPathbeforeFile.OpenRead, preserving PowerShell provider/literal relative-path semantics;Get-FileHashto 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-widejust fmt; its unrelated Bazel/Starlark step could not launchbuildifier, and Windows Application Control blockeduvfor 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.
Positive field result on 2026-08-28, but not yet evidence that the general bug is fixed.
Environment:
28000, ARM64codex-cli 0.150.1codex.execreation time confirms that this update completed on 2026-08-28For the first time on this machine, the normal Codex update completed from PowerShell without opening a separate
cmd.exewindow as a workaround.However, a safe post-update process-chain probe still reproduces the root mechanism described in this issue:
pwsh 7.6.5 -> powershell.exe 5.1:Get-FileHashresolves fromMicrosoft.PowerShell.Utility 3.1.0pwsh 7.6.5 -> native intermediary -> powershell.exe 5.1:Get-FileHashdoes not resolveThe currently served
install.ps1still callsGet-FileHash, and the0.150.1standalone update action still launchespowershell.exewithout removing or normalizingPSModulePath.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.