Repository navigation
[Bug]: Codex "Update now" runs npm install -g against installs it never verified are npm-managed, causing a permanent "still needs an update" loop #5629
Description
Activity
On Windows standalone installs,
codex updatedelegates topowershell.exe -ExecutionPolicy Bypass -c "$env:CODEX_NON_INTERACTIVE=1; irm .../install.ps1 | iex". Upstream Codex has an open Windows bug wherepowershell.exeinherits PowerShell 7PSModulePathentries from its parent environment, causing module resolution (notablyGet-FileHash) to fail.Relevant upstream issues:
- openai/codex#27117 — identifies
PSModulePathinheritance as the cause; removing/normalizing it before spawning Windows PowerShell fixes the reproduction. - openai/codex#30015 — reports the same failure from a real native Windows install during
codex update.
So if this issue is fixed by adding
nativeUpdate: { executable: "codex", args: ["update"], ... }, I think the Windows path deserves a regression test / explicit consideration.There are two ownership options:
- Preferable: T3 invokes
codex updateand leaves PowerShell details to Codex. Once Windows standalone update from pwsh inherits PSModulePath into powershell.exe, causing Get-FileHash to fail openai/codex#27117 is fixed upstream, T3 automatically benefits. - Defensive T3 workaround: if T3's environment passed to
codexcan contain a PowerShell-7PSModulePath, sanitize that variable for the update subprocess on Windows. This avoids propagating an environment known to break Codex's subsequentpowershell.exechild.
I would avoid having T3 duplicate the install.ps1 invocation itself, since that hard-codes Codex's updater implementation into T3 and could drift from upstream.
It may also be worth testing:
- standalone Codex launched from a normal GUI/T3 environment;
- T3 launched from Windows PowerShell 5.1;
- T3 launched from PowerShell 7 with a PowerShell-7
PSModulePath; - npm-managed Codex, to ensure native-update detection doesn't steal that path;
- an unknown/unclassified Codex install, which should become manual-only rather than falling back to npm.
Reacted by Abdurrafay Bin Khurram- openai/codex#27117 — identifies
Confirmed on a current headless macOS Apple Silicon setup running T3 Code
0.0.34-nightly.20260824.1174.Setup:
- Codex installed with OpenAI's official standalone installer
- active executable:
~/.local/bin/codex->~/.codex/packages/standalone/releases/<version>-aarch64-apple-darwin/bin/codex - two T3 provider instances share that executable while using separate
CODEX_HOMEdirectories - both instances use an explicit absolute
binaryPath
When Codex
0.149.0fell behind npm latest0.149.1, both T3 snapshots reported:{ "status": "behind_latest", "currentVersion": "0.149.0", "latestVersion": "0.149.1", "canUpdate": false, "updateCommand": null, "message": "Install the update now or review provider settings." }So T3 correctly avoided the unsafe npm fallback in this explicit-path variant, but the UI could only show the update advisory and offered no one-click update.
The installed CLI exposes the supported native command:
codex update Update Codex to the latest versionRunning that command against the same standalone executable updated it successfully:
Updating Codex CLI from 0.149.0 to 0.149.1 Codex CLI 0.149.1 installed successfully.Both isolated profiles then resolved the shared executable as
codex-cli 0.149.1without changing their account homes.This is a real multi-instance confirmation that wiring
nativeUpdateto the resolved standalone executable withargs: ["update"]is the correct behavior. It also avoids creating or maintaining a second npm-owned Codex installation.Related implementation candidates already open: #5630 and #4065; the broader installation-aware work is in #6436.
Confirming this on Linux, with a harder failure mode than the one in the report: the same root cause produces a non-zero exit instead of a silent no-op.
Setup — Codex installed with OpenAI's standalone installer, exactly the layout described in the issue:
~/.local/bin/codex -> ~/.codex/packages/standalone/current/bin/codex -> releases/0.151.0-x86_64-unknown-linux-musl $ codex --version codex-cli 0.151.0binaryPathleft at the default bare"codex". npm's global prefix is~/.local.What the UI shows:
Provider update failed — Update command exited with code 1.Reproduced by hand, running the exact command the runner builds:
$ npm install -g --allow-scripts=@openai/codex @openai/codex@latest npm error code EEXIST npm error path ~/.local/bin/codex npm error EEXIST: file already exists npm error File exists: ~/.local/bin/codex npm error Remove the existing file and try again, or run npm npm error with --force to overwrite files recklessly.Exit code 1.
~/.local/lib/node_modules/contains no@openai/codex— npm never owned this install.Why this variant differs. The original report's machine had npm prefix
/opt/homebrew, sonpm install -gsucceeded and quietly maintained a second, unused copy — hence the permanent "still needs an update" loop. Here npm's prefix is~/.local, which is the samebindirectory the standalone installer owns, so npm's bin-link step collides with the installer's symlink and aborts outright. Same defect, two symptoms, differing only in where npm's prefix happens to point:npm prefix collides with ~/.local/bin/codex?result elsewhere (e.g. /opt/homebrew)no exit 0, shadow install, permanent "still needs an update" ~/.localyes exit 1, EEXIST, "Update command exited with code 1"Neither is recoverable from the UI.
codex updatefrom a terminal works correctly in both cases, which is the fix this issue already proposes.Still present on current
main:apps/server/src/provider/providerMaintenance.tsis unchanged, andapps/server/src/provider/Drivers/CodexDriver.ts:68still passesnativeUpdate: null.Worth noting for anyone picking this up: #4065, which implements the
nativeUpdatepath, is currentlyCONFLICTINGagainstmainand has been idle since 2026-07-29.Reacted by Hjalmar KarlsenStill reproduces on current main (
c17d02cff); line refs have drifted since5661c6116— the bare-name fallback is nowproviderMaintenance.ts:321, andCodexDriver.ts:68still passesnativeUpdate: null.#5646 also changed the emitted command, so what runs today is:
npm install -g --allow-scripts=@openai/codex @openai/codex@latest(Adding only the drift check — the Linux repro and the
codex updateconfirmation are already covered above by @Dropje97 and @marcob896.)Reacted by Hjalmar KarlsenAnother Linux variant, on Arch (CachyOS), where both providers are owned by the system package manager and npm's global prefix is root-owned. This fails harder than the cases above: no in-app action can ever succeed, because the command targets a directory the app's user cannot write.
Setup
$ pacman -Qo /usr/bin/claude /usr/bin/claude is owned by claude-code 2.1.251-1 $ pacman -Qo /usr/bin/codex /usr/bin/codex is owned by openai-codex 0.151.0-1.1 $ npm prefix -g /usr $ npm root -g /usr/lib/node_modules $ stat -c '%U:%G %a' /usr/lib/node_modules root:root 755
binaryPathleft at the default bare value for both providers.What the UI shows:
Provider update failed — Update command exited with code 1.Interestingly this lands as two distinct defects, one per provider.
codex — the bare-name npm assumption, exactly as reported
There is no
@openaiscope in the global root at all:$ ls /usr/lib/node_modules/ @anthropic-ai node-gyp nopt npm semver
/usr/bin/codexis a 251 MB standalone binary shipped by the distro package. npm has never owned it.providerMaintenance.ts:321classifies it as npm-managed purely becausebinaryPathhas no path separator.claude — npm detection is right, but the target is unwritable, and the divergence in the issue title has already materialized
There really is an npm copy:
$ npm ls -g --depth=0 /usr/lib ├── @anthropic-ai/claude-code@2.1.251 ... $ pacman -Qo /usr/lib/node_modules/@anthropic-ai/claude-code error: No package owns /usr/lib/node_modules/@anthropic-ai/claude-code
...but it is not what PATH resolves to.
/usr/bin/claudeis a distro wrapper:#!/bin/sh export DISABLE_UPDATES=1 export DISABLE_INSTALLATION_CHECKS=1 exec /opt/claude-code/bin/claude "$@"
So the probed install is
/opt/claude-code/bin/claude(214 MB, pacman-owned), while the npm copy under/usr/lib/node_modulesis an orphan nothing ever executes — the "silently maintained an npm copy I was never running" outcome from the original report, already present here before any update was attempted. Annpm install -gwould bump the orphan and leave the probe reading the untouched pacman install: the permanent loop in the title. It never gets that far, because the write fails first.Suggested check
Worth noting that prefix containment will not separate these —
/usr/bin/codexis insidenpm prefix -g(/usr) while being entirely unrelated to npm. Two cheap checks would cover every variant reported on this issue:- Before assuming npm ownership, confirm npm actually lists the package (
npm ls -g --depth=0 --json, or that$(npm root -g)/<pkg>exists). That alone disqualifies codex here, and the standalone-installer cases above. - Before setting
canUpdate: true, confirm the process can writenpm root -gand$(npm prefix -g)/bin. That covers this case and any root-owned or read-only prefix, where the right answer is an advisory plus the manual command rather than a button.
Both are also the honest answer for a distro-managed install: T3 Code cannot drive
pacman, and shouldn't try.Environment: T3 Code 0.0.38-1 (AUR
t3code-bin), CachyOS (Arch), npm 12.0.2, Node 26.7.0.- Before assuming npm ownership, confirm npm actually lists the package (
Confirmed again on the current Linux nightly with the exact UI sequence shown in the report:
- T3 Code
0.0.38-nightly.20260901.1250 - Linux x86_64
- Codex
0.152.0, installed by the official standalone installer - T3 offers Codex
0.152.1; clicking Update ends withCodex v0.152.1 update failed
The active executable is:
~/.local/bin/codex -> ~/.codex/packages/standalone/current/bin/codex -> ~/.codex/packages/standalone/releases/0.152.0-x86_64-unknown-linux-musl/bin/codexHowever, T3 advertises and runs:
npm install -g --allow-scripts=@openai/codex @openai/codex@latestThere is no npm-managed
@openai/codexon this machine. Because npm's global prefix is~/.local, its bin-link step collides with the standalone installer's existing symlink:npm error code EEXIST npm error path ~/.local/bin/codex npm error File exists: ~/.local/bin/codexAfter the failed action, the provider remains on
0.152.0and continues advertising0.152.1withcanUpdate: trueand the npm command above. The installed CLI exposes the supported native command:codex update Update Codex to the latest versionThis is the provider-maintenance parity gap with Claude: standalone Codex installs should use the resolved executable's native
codex updatepath, as Claude's driver does forclaude update. An unclassified install should be manual-only rather than assumed to be npm-owned.- T3 Code
Additional cross platform confirmation:
- On Windows, clicking
Update nowerrors. - On macOS, my active Codex installation is from OpenAI’s standalone installer. T3 nevertheless offered and ran
npm install -g --allow-scripts=@openai/codex @openai/codex@latest.
The npm command succeeded, but it installed a separate npm managed Codex instead of updating the standalone installer I actually use. This is undesirable even though it does not surface as an error on macOS: T3 mutates a second installation while leaving the active one untouched.
For standalone installs, T3 should use
codex update, or treat the installation as manual instead of falling back to npm.The attached screenshots show the npm command T3 offered and its execution after clicking Update now.

- On Windows, clicking
- added 4 commits that reference this issue
on Sep 3, 2026
Before submitting
Area
apps/server
Steps to reproduce
~/.local/bin/codexpointing into~/.codex/packages/standalone/current/bin/codex.binaryPathat its default bare value"codex"(packages/contracts/src/settings.ts:203).@openai/codexnpm release.Expected behavior
Either:
codex updatesubcommand for exactly this install method), orcanUpdate: false, and surface the correct manual command — the same way the path-separator branch already does.In no case should T3 Code run a package-manager install command against an install it has no evidence that package manager owns.
Actual behavior
The update reports
Provider still needs an update/Codex still appears outdated. Check provider settings for details.every time, forever.What actually happens is worse than a cosmetic loop: T3 Code runs
npm install -g @openai/codex@latest, which updates (or newly creates) a different codex install than the one it probes. On my machine it silently maintained an npm copy at/opt/homebrew/lib/node_modules/@openai/codexthat I was never running, while the probe kept reading the standalone install on PATH.Trace through
origin/main:CodexDriver.ts:147resolves maintenance capabilities withbinaryPath: "codex".resolveProviderMaintenanceCapabilitiesEffect(providerMaintenance.ts:346) resolves that through PATH andrealPaths it to~/.codex/packages/standalone/releases/<ver>/bin/codex.resolvePackageManagedProviderMaintenance(providerMaintenance.ts:266) that path matches no heuristic — not/node_modules/, not/Cellar/, not.bun/pnpm/vite-plus — andCodexDriver.ts:67passesnativeUpdate: null, so the native branch is skipped too.providerMaintenance.ts:311:if (!hasPathSeparator(binaryPath)) return makeNpmGlobalProviderMaintenanceCapabilities(...). BecausebinaryPathis the bare string"codex", an unclassifiable install is assumed to be npm and getscanUpdate: true.providerMaintenanceRunner.ts:342runsnpm install -g @openai/codex@latest, which exits 0.providerMaintenanceRunner.ts:356-377re-probes, still resolves the untouched standalone binary, still seesbehind_latest, and correctly recordsstatus: "unchanged".ProviderUpdateLaunchNotification.logic.ts:279renders the toast.The runner's verification is working exactly as designed — it refuses to claim a success that did not happen. The defect is upstream of it, in capability resolution.
Two separable defects:
1.
CodexDrivernever opts into codex's native updater.ClaudeDriver.ts:63-82already handles the byte-for-byte analogous layout for Claude:CodexDriver.ts:63-68passesnativeUpdate: nulldespitecodex updateexisting (codex --help:update — Update Codex to the latest version) and despite the standalone installer using the same~/.local/binconvention. This reads as an unfinished port rather than a deliberate choice.2. The bare-name fallback guesses instead of declining. The same binary produces opposite behavior depending only on how it is spelled in settings:
binaryPath"codex"~/.codex/packages/standalone/.../bin/codex:311canUpdate: true→ runsnpm install -g"/Users/me/.local/bin/codex":315manual-only,canUpdate: false→ no loopIdentical install, identical failed classification, opposite outcome. The path-separator branch gets it right; the bare-name branch converts "unknown install method" into "npm" and then mutates global state on that guess. Fixing defect 1 alone would leave this trap for the next unrecognized install layout.
Impact
Major degradation or frequent failure
Version or commit
origin/main@5661c6116(apps/server 0.0.31). Confirmed present on current main —providerMaintenance.ts,CodexDriver.tsandproviderMaintenanceRunner.tsare unchanged there.Environment
~/.local/bin/codex→~/.codex/packages/standalone/current/bin/codex@openai/codex@lateston npm at the time: 0.147.0binaryPath: default"codex"/opt/homebrew/bin/codex— but note this reproduces with no npm copy at all, in which casenpm install -gcreates one the user never asked for.Logs or stack traces
codex doctorreports the install method unambiguously, and could be a cheap detection signal:On a standalone install the same command reports an installer-managed root instead.
Workaround
Point the provider at an install T3 Code can classify — set
binaryPathto the absolute npm shim path (e.g./opt/homebrew/bin/codex), whoserealPathlands under/lib/node_modules/and so matchesisNpmGlobalCommandPath. This keepscanUpdate: trueand makes the provider immune to PATH shadowing. Removing the shadowing standalone symlink also works but leaves the underlying guess in place.Prior report
#3550 describes the same user-visible symptom and was closed
NOT_PLANNED. It appears never to have been triaged on merit — the sole comment is the reporter noting a bot had filed spurious issues, and this got swept up in that cleanup.Its stated hypothesis was missing Bun support, and that hypothesis was almost certainly wrong:
isBunGlobalCommandPathlanded 2026-05-05 in #2312, roughly seven weeks before #3550 was filed. Bun was already handled. That reporter most likely hit this same:311fallback and misattributed it — which is why I'm filing fresh rather than asking to reopen.Suggested fix
Both parts are small:
realCommandPathis already threaded throughresolveProviderMaintenanceCapabilitiesEffect(providerMaintenance.ts:366-375), so the/.codex/packages/standalone/match fires through the symlink regardless of the link's name.For defect 2, the
:311fallback should returnmakeManualOnlyProviderMaintenanceCapabilitiesonce a command path was successfully resolved but matched nothing — reserving the npm assumption for the case where resolution failed entirely and there is genuinely nothing to go on. Surfacing "T3 Code can't auto-update this install method" is strictly better than silentlynpm install -g-ing a package the user may not have.providerMaintenance.test.tsalready covers the per-manager branches; these would slot in alongside.