Detect Codex installs managed by pnpm (#31503)

## Why

The Codex JavaScript shim currently distinguishes npm and Bun installs,
but a global pnpm install falls back to npm. That causes the native CLI,
`codex doctor`, and update flows to report or run npm commands even
though pnpm owns the installation. pnpm also allows its global package
and bin directories to differ, so `PNPM_HOME` does not reliably identify
the package owner.

## What changed

- detect pnpm-managed installs by finding pnpm's
`node_modules/.modules.yaml` metadata from the launched JavaScript
entrypoint
- pass a mutually exclusive `CODEX_MANAGED_BY_PNPM` marker into the
native CLI
- represent pnpm in install context, doctor output, version checks, and
TUI update actions without changing the existing public
`InstallContext::from_exe` signature
- recommend `pnpm add -g @openai/codex` for pnpm-managed installs and
snapshot the rendered TUI update notice

Validated with the 9-test install-context suite, the focused pnpm TUI
snapshot test, and Node's syntax check for the launcher.

Closes https://github.com/openai/codex/issues/10294.
This commit is contained in:
Charlie Marsh authored and GitHub committed 2026-07-08 12:17:58 -04:00
1 parent 0bbea86a6a
commit e212cc95b0
12 files changed
+152 -50

No files matched your search

+1 -1
View File
@@ -1260,7 +1260,7 @@ Environment
LESS -FRX
✓ runtime running local build on darwin-arm64
✓ install consistent
managed by npm: no · bun: no · package root —
managed by npm: no · bun: no · pnpm: no · package root —
✓ search search is OK (bundled)
✓ git git version 2.54.0
selected git /usr/bin/git
+4 -1
View File
@@ -211,13 +211,15 @@ fn install_details(parsed: &[ParsedDetail], options: HumanOutputOptions) -> Vec<
let managed_by_npm = value(parsed, "managed by npm").unwrap_or("false");
let managed_by_bun = value(parsed, "managed by bun").unwrap_or("false");
let managed_by_pnpm = value(parsed, "managed by pnpm").unwrap_or("false");
let package_root = value(parsed, "managed package root").unwrap_or("not set");
out.push(HumanDetail::Row {
label: "managed by".to_string(),
value: format!(
"npm: {} · bun: {} · package root {}",
"npm: {} · bun: {} · pnpm: {} · package root {}",
yes_no(managed_by_npm),
yes_no(managed_by_bun),
yes_no(managed_by_pnpm),
if is_falsy(package_root) {
"—".to_string()
} else {
@@ -263,6 +265,7 @@ fn install_details(parsed: &[ParsedDetail], options: HumanOutputOptions) -> Vec<
"install context",
"managed by npm",
"managed by bun",
"managed by pnpm",
"managed package root",
"PATH codex entries",
],
+1
View File
@@ -121,6 +121,7 @@ fn install_method_name(context: &InstallContext) -> &'static str {
InstallMethod::Standalone { .. } => "standalone",
InstallMethod::Npm => "npm",
InstallMethod::Bun => "bun",
InstallMethod::Pnpm => "pnpm",
InstallMethod::Brew => "brew",
InstallMethod::Other => "local build",
}
@@ -21,7 +21,7 @@ Environment
LESS -FRX
✓ runtime running local build on darwin-arm64
✓ install consistent
managed by npm: no · bun: no · package root —
managed by npm: no · bun: no · pnpm: no · package root —
✓ search search is OK (bundled)
✓ git git version 2.54.0
selected git /usr/bin/git
+9
View File
@@ -133,6 +133,7 @@ fn update_action_label(context: &InstallContext) -> &'static str {
match &context.method {
InstallMethod::Npm => "npm install -g @openai/codex",
InstallMethod::Bun => "bun install -g @openai/codex",
InstallMethod::Pnpm => "pnpm add -g @openai/codex",
InstallMethod::Brew => "brew upgrade --cask codex",
InstallMethod::Standalone { .. } => "standalone installer",
InstallMethod::Other => "manual or unknown",
@@ -144,6 +145,7 @@ fn fetch_latest_version(context: &InstallContext) -> Result<String, String> {
InstallMethod::Brew => fetch_homebrew_cask_version(),
InstallMethod::Npm
| InstallMethod::Bun
| InstallMethod::Pnpm
| InstallMethod::Standalone { .. }
| InstallMethod::Other => fetch_latest_github_release_version(),
}
@@ -223,6 +225,13 @@ mod tests {
}),
"npm install -g @openai/codex"
);
assert_eq!(
update_action_label(&InstallContext {
method: InstallMethod::Pnpm,
package_layout: None,
}),
"pnpm add -g @openai/codex"
);
assert_eq!(
update_action_label(&InstallContext {
method: InstallMethod::Other,