Change CLI update message presentation to reduce confusion
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
- Run a version of Codex CLI that is behind the latest release.
- 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 │
╰───────────────────────────────────────────────────╯
- 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.
- User strips
Runfrom the front (or copies fromnpmonward), pastes into a terminal, and runs it:
npm install -g @openai/codex to update
- npm interprets
toandupdateas additional package names and installs all three globally.
Observed outcome
Three packages installed at the same timestamp:
@openai/codextoupdate
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 findingsupdate@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
- 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.
- Use
@latestconsistently in the runtimecommand_args()to match the snapshot test expectations. - 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
5 Comments
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.rsat line 674: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 becomesRun npm install -g @openai/codex to update.— and if you copy fromnpmonward, npm happily installstoandupdateas packages. The same pattern is in theraw_lines()fallback at line 707.Proposed fix
Split the instruction into a label line and a command-only line:
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-40produces@openai/codexwithout@latestfor 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.
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" />
@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.
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
@latestissueSeparately, and independently of the colour debate, the missing
@latestincommand_args()is causing a real update loop for users. When the TUI triggers the auto-update it runsnpm 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: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.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
@latestinconsistency betweenupdate_action.rsand 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.