Change CLI update message presentation to reduce confusion

Resolved 💬 5 comments Opened May 9, 2026 by NickMillerUK Closed May 9, 2026
💡 Likely answer: A maintainer (etraut-openai, contributor) responded on this thread — see the highlighted reply below.

Summary

The Codex CLI update notification (the box prompt rendered by UpdateAvailableHistoryCell) places the npm command and explanatory prose on the same line, making it easy to accidentally copy trailing words as if they were part of the command. This causes npm to install stray packages globally.

Steps to reproduce

  1. Run a version of Codex CLI that is behind the latest release.
  2. The update box prompt appears:
╭───────────────────────────────────────────────────╮
│ ✨ Update available! 0.116.0 -> 0.118.0            │
│ Run npm install -g @openai/codex to update.        │
│                                                    │
│ See full release notes:                            │
│ https://github.com/openai/codex/releases/latest   │
╰───────────────────────────────────────────────────╯
  1. User selects and copies the command line. Without colour context (which is lost on copy), the full visible string is:
Run npm install -g @openai/codex to update.
  1. User strips Run from the front (or copies from npm onward), pastes into a terminal, and runs it:
npm install -g @openai/codex to update
  1. npm interprets to and update as additional package names and installs all three globally.

Observed outcome

Three packages installed at the same timestamp:

  • @openai/codex
  • to
  • update

Root cause

codex-rs/tui/src/history_cell.rs, line 674:

line!["Run ", update_action.command_str().cyan(), " to update."]

The command (npm install -g @openai/codex) is rendered in cyan, and " to update." is plain text — visually distinct by colour, but indistinguishable on copy. The same pattern is in the raw_lines() plaintext fallback at line 707:

format!("Run {} to update.", update_action.command_str())

Security impact

to and update are real npm packages with known vulnerabilities:

  • to@0.2.9 — critical audit findings
  • update@0.7.4 — multiple critical/high/moderate audit findings

Users who follow the update prompt as intended end up with vulnerable packages silently added to their global npm environment.

Secondary issue: missing @latest tag

codex-rs/tui/src/update_action.rs, line 39:

UpdateAction::NpmGlobalLatest => ("npm", &["install", "-g", "@openai/codex"]),

The runtime command omits @latest. The snapshot test (codex_tui__update_prompt__tests__update_prompt_modal.snap) shows @openai/codex@latest, so the test fixture and runtime are inconsistent. Without @latest, npm may resolve a cached or older version depending on local registry state.

Recommended fixes

  1. Separate the command from the prose. Never render a shell command and explanatory text on the same copyable line. Use a dedicated line or a copy-action button for the command itself.
  2. Use @latest consistently in the runtime command_args() to match the snapshot test expectations.
  3. Consider a one-click / confirmation update from the TUI (Variant B already moves toward this with the interactive menu — Variant A should follow the same pattern or be removed).

References

  • Related issues: #12107, #7104, #16488, #5012

View original on GitHub ↗

5 Comments

NickMillerUK · 3 months ago

I've dug into the source and have a fix ready if useful.

Root cause confirmed

The bug is in codex-rs/tui/src/history_cell.rs at line 674:

line!["Run ", update_action.command_str().cyan(), " to update."]

The command is styled cyan but " to update." is plain text on the same line. Colour context is lost on copy, so the full visible string becomes Run npm install -g @openai/codex to update. — and if you copy from npm onward, npm happily installs to and update as packages. The same pattern is in the raw_lines() fallback at line 707.

Proposed fix

Split the instruction into a label line and a command-only line:

│ To update, run:                                    │
│   npm install -g @openai/codex@latest              │

This matches the safer pattern already used by the Variant B interactive modal (update_prompt.rs:216), where the command is isolated inside backticks on its own menu item.

I also noticed that update_action.rs:39-40 produces @openai/codex without @latest for npm and bun, while the existing snapshot test (update_prompt_modal.snap) already expects @openai/codex@latest — so those two lines are inconsistent. The fix would align the runtime code with the snapshot.

Both changes are small and isolated. Happy to submit a PR if the team is open to it.

etraut-openai contributor · 3 months ago

The message is already presented using coloring that clearly delineates the command from the surrounding text. I think this provides sufficient clarity.

<img width="371" height="100" alt="Image" src="https://github.com/user-attachments/assets/904368dc-0097-4ea4-8569-f8dca0b4aa6d" />

NickMillerUK · 3 months ago

@etraut-openai on a dark background they are almost indistinguishable and most developers don’t use light backgrounds on terminal.

I don’t think the colour differences meet accessibility standards either and I have actual examples of users not realising and copying the entire string and installing unsafe npm packages globally as a result.

It’s a tiny fix and I have a PR for it and it will save unnecessary issues for users down stream. Once the bad packages are installed npm will alert about them and it will take users some take to figure out how they got there and fix it.

NickMillerUK · 3 months ago

On the colour distinction

I ran the WCAG contrast numbers. The issue isn't whether cyan or white are individually readable on a dark background — they both are (cyan vs black: 16.75:1, white vs black: 21:1). The problem is the contrast between them, which is what a user needs to perceive the boundary between the command and the prose:

| Colours | Contrast ratio | WCAG AA minimum |
|---|---|---|
| ANSI cyan (#00FFFF) vs white (#FFFFFF) | 1.25:1 | 4.5:1 |
| xterm cyan (#00CDCD) vs white (#FFFFFF) | 1.98:1 | 4.5:1 |
| ANSI cyan vs grey fg (#CCCCCC) | 1.28:1 | 4.5:1 |

Every variant fails by a wide margin. The relevant criterion is WCAG 1.4.1 Use of Color (Level A) — the most basic accessibility level — which states that colour must not be the only visual means of distinguishing information. Here it is: there is no other visual cue (no quotes, no pipe, no line break, no bold/italic) separating the command from the prose.

On the @latest issue

Separately, and independently of the colour debate, the missing @latest in command_args() is causing a real update loop for users. When the TUI triggers the auto-update it runs npm install -g @openai/codex (no @latest), npm sees the package as already at the latest cached version and does nothing, and the update prompt reappears on next launch. This is the root cause behind at least:

  • #12107 — user stuck in update loop, update reports success but version never changes
  • #7104 — same on WSL2, update never actually applies

The snapshot test already expects @openai/codex@latest — the runtime just doesn't match it. That fix is one character change and is independent of any UI decision about the prompt wording.

NickMillerUK · 3 months ago

Correction on the linked issues

I looked more carefully at #12107 and #7104 and they were caused by different things — #12107 was a pnpm/npm collision and #7104 was a failed 0.62.0 publish that never reached npm. Neither was caused by the missing @latest. I shouldn't have cited them as supporting evidence for that point.

The @latest inconsistency between update_action.rs and the snapshot test still stands as a code-level oddity worth fixing, but I don't have evidence it is causing user-facing problems in the field.