Add codex update command (#19933)

## Why

Addresses #9274

Running `codex update` currently starts an interactive Codex session
with `update` as the prompt. That is a rough edge for users who expect a
direct self-update command after seeing the existing update notice, and
it forces them to copy the suggested package-manager command manually.

## What changed

- Added a top-level `codex update` subcommand.
- Reused the existing install-channel detection and update command
runner that the TUI already uses for update prompts.
- Exposed the update-action lookup from `codex-tui` so the CLI can
invoke the same behavior.
- Added CLI coverage to ensure `codex update` is parsed as a subcommand
instead of becoming an interactive prompt.

## Verification

- `cargo test -p codex-cli`
- `cargo test -p codex-tui update_action::tests`
This commit is contained in:
Eric Traut
2026-04-27 23:33:59 -07:00
committed by GitHub
Unverified
parent 0a32c8b396
commit b985768dc1
5 changed files with 64 additions and 2 deletions
+36
View File
@@ -134,6 +134,9 @@ enum Subcommand {
/// Generate shell completion scripts.
Completion(CompletionCommand),
/// Update Codex to the latest version.
Update,
/// Run commands within a Codex-provided sandbox.
Sandbox(SandboxArgs),
@@ -615,6 +618,25 @@ fn run_update_action(action: UpdateAction) -> anyhow::Result<()> {
Ok(())
}
fn run_update_command() -> anyhow::Result<()> {
#[cfg(debug_assertions)]
{
anyhow::bail!(
"`codex update` is not available in debug builds. Install a release build of Codex to use this command."
);
}
#[cfg(not(debug_assertions))]
{
let Some(action) = codex_tui::get_update_action() else {
anyhow::bail!(
"Could not detect the Codex installation method. Please update manually: https://developers.openai.com/codex/cli/"
);
};
run_update_action(action)
}
}
fn run_execpolicycheck(cmd: ExecPolicyCheckCommand) -> anyhow::Result<()> {
cmd.run()
}
@@ -998,6 +1020,14 @@ async fn cli_main(arg0_paths: Arg0DispatchPaths) -> anyhow::Result<()> {
)?;
print_completion(completion_cli);
}
Some(Subcommand::Update) => {
reject_remote_mode_for_subcommand(
root_remote.as_deref(),
root_remote_auth_token_env.as_deref(),
"update",
)?;
run_update_command()?;
}
Some(Subcommand::Cloud(mut cloud_cli)) => {
reject_remote_mode_for_subcommand(
root_remote.as_deref(),
@@ -1890,6 +1920,12 @@ mod tests {
assert!(matches!(cli.subcommand, Some(Subcommand::Plugin(_))));
}
#[test]
fn update_parses_as_update_subcommand() {
let cli = MultitoolCli::try_parse_from(["codex", "update"]).expect("parse");
assert!(matches!(cli.subcommand, Some(Subcommand::Update)));
}
#[test]
fn plugin_marketplace_remove_parses_under_plugin() {
let cli =