Skip to content

check for updates #1764

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Aug 2, 2025
Merged

check for updates #1764

merged 11 commits into from
Aug 2, 2025

Conversation

nornagon-openai
Copy link
Collaborator

  1. Ping https://api.github.com/repos/openai/codex/releases/latest (at most once every 20 hrs)
  2. Store the result in ~/.codex/version.jsonl
  3. If CARGO_PKG_VERSION < latest_version, print a message at boot.

@nornagon-openai nornagon-openai added the codex-rust-review Perform a detailed review of Rust changes. label Jul 31, 2025
@github-actions github-actions bot added codex-rust-review-in-progress and removed codex-rust-review Perform a detailed review of Rust changes. labels Jul 31, 2025

This comment was marked as resolved.

Comment on lines 152 to 158
let update_command = if cfg!(target_os = "macos")
&& (exe.starts_with("/opt/homebrew") || exe.starts_with("/usr/local"))
{
"brew upgrade codex"
} else {
"npm install -g @openai/codex@latest"
};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To recommend npm install, I think we should add the check in here:

https://github.com/openai/codex/blob/main/codex-cli/bin/codex.js

I think we should have a special build-time env var when building for Homebrew:

https://github.com/bolinfest/homebrew-core/blob/main/Formula/c/codex.rb

And check it with env!() and set the update_command, as appropriate.

If there is no valid update_command, we should point the user to https://github.com/openai/codex/releases/latest (maybe even the artifact within latest, though we have to commit to the artifact names to future-proof it)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i feel it's a bummer to have to build O(N*M) binaries for N distribution channels on M platforms. granted brew is only on one platform, but apt-get x snap x pacman x ...

it would be good if this were an install-time configuration. when the package manager installs the package, it writes out a system-wide config (/etc/codex/config.toml?) that contains the command to upgrade.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I generally agree that seems better, though I've been trying to think through that could be abused by a bad actor in some way.

Copy link
Collaborator Author

@nornagon-openai nornagon-openai Aug 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think if as a bad actor you can edit files in /etc, you have already won. /etc/hosts for instance, or /etc/zshrc.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, though neither npm install -g nor brew install should be run as root, so they can't write /etc either?

@easong-openai
Copy link
Contributor

I think we need some notification for users that they're out of date ASAP - how about passing in some signal from the node script to let the rust app know it's managed by NPM, then we can detect that and display an npm message, followed by a homebrew message if we're on macos and then a generic message otherwise?

Ok(serde_json::from_str(&contents)?)
}

async fn update_version(version_file: &Path) -> anyhow::Result<()> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a docstring and/or rename? This doesn't update the version of the CLI, but the version file with metadata, is that right?

Ok(())
}

fn is_newer(latest: &str, current: &str) -> Option<bool> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please add some unit tests so we can be sure that something like 0.11.0-beta.1 always fails the is_newer() check? I believe that is the case right now, but I want to be sure we don't regress that.

let current_version = env!("CARGO_PKG_VERSION");
let exe = std::env::current_exe()?;
let update_command = if cfg!(target_os = "macos")
&& (exe.starts_with("/opt/homebrew") || exe.starts_with("/usr/local"))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't we drop exe.starts_with("/usr/local")) for now? There's a lot of ways one can end up with /usr/local/bin/codex and I'm not sure how often I believe it is brew install.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If someone didn't install it through the recommended installation paths I think okay if they have to figure out how to update?

None => true,
Some(info) => info.last_checked_at < Utc::now() - Duration::hours(20),
} {
tokio::spawn(async move {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we spawn this, but nothing waits for it? I'm a bit confused what the expectation here is.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems okayish - check for update on startup, then you get a banner the next time you open it. That way we don't wait for the network call in order to start.

Copy link
Collaborator

@bolinfest bolinfest left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All small stuff: thanks for doing this!

@@ -42,6 +42,9 @@ ratatui = { version = "0.29.0", features = [
ratatui-image = "8.0.0"
regex-lite = "0.1"
serde_json = { version = "1", features = ["preserve_order"] }
serde = { version = "1", features = ["derive"] }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alpha-sort?

@@ -139,6 +144,38 @@ pub async fn run_main(
.with(tui_layer)
.try_init();

#[allow(clippy::print_stderr)]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Low pri, but I would consider moving this to updates.rs so you can have a smaller thing attached to #[cfg(not(debug_assertions))].

})
}

#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I use use serde::Deserialize and Deserialize at the top, FYI.

last_checked_at: chrono::DateTime<chrono::Utc>,
}

const VERSION_FILENAME: &str = "version.jsonl";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not just .json?

Comment on lines 49 to 51
let mut path = config.codex_home.clone();
path.push(VERSION_FILENAME);
path
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be possible to do something like this?

Suggested change
let mut path = config.codex_home.clone();
path.push(VERSION_FILENAME);
path
config.codex_home.join(VERSION_FILENAME).to_path_buf()

}

async fn check_for_update(version_file: &Path) -> anyhow::Result<()> {
#[derive(serde::Deserialize, Debug, Clone)]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would just make this a top level struct, but up to you.

latest_version: latest_tag_name
.strip_prefix("rust-v")
.ok_or_else(|| {
anyhow::anyhow!("Failed to parse latest tag name '{}'", latest_tag_name)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
anyhow::anyhow!("Failed to parse latest tag name '{}'", latest_tag_name)
anyhow::anyhow!("Failed to parse latest tag name '{latest_tag_name}'")


let json_line = format!("{}\n", serde_json::to_string(&info)?);
if let Some(parent) = version_file.parent() {
tokio::fs::create_dir_all(parent).await.ok();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why ok() instead of ? (and two lines down)

tag_name: String,
}

let resp = reqwest::Client::new()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feels like a good candidate for destructuring:

Suggested change
let resp = reqwest::Client::new()
let ReleaseInfo { tag_name: latest_tag_name} = reqwest::Client::new()

and then you can remove:

let latest_tag_name = resp.tag_name;

@easong-openai easong-openai enabled auto-merge (squash) August 2, 2025 00:22
@easong-openai easong-openai disabled auto-merge August 2, 2025 00:23
@easong-openai easong-openai enabled auto-merge (squash) August 2, 2025 00:25
@easong-openai easong-openai merged commit 7e0f506 into main Aug 2, 2025
11 checks passed
@easong-openai easong-openai deleted the nornagon/check-updates branch August 2, 2025 00:31
@github-actions github-actions bot locked and limited conversation to collaborators Aug 2, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants