-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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
check for updates #1764
Conversation
nornagon-openai
commented
Jul 31, 2025
- Ping https://api.github.com/repos/openai/codex/releases/latest (at most once every 20 hrs)
- Store the result in ~/.codex/version.jsonl
- If CARGO_PKG_VERSION < latest_version, print a message at boot.
This comment was marked as resolved.
This comment was marked as resolved.
codex-rs/tui/src/lib.rs
Outdated
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" | ||
}; |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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?
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? |
codex-rs/tui/src/updates.rs
Outdated
Ok(serde_json::from_str(&contents)?) | ||
} | ||
|
||
async fn update_version(version_file: &Path) -> anyhow::Result<()> { |
There was a problem hiding this comment.
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> { |
There was a problem hiding this comment.
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")) |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this 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!
codex-rs/tui/Cargo.toml
Outdated
@@ -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"] } |
There was a problem hiding this comment.
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)] |
There was a problem hiding this comment.
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))]
.
codex-rs/tui/src/updates.rs
Outdated
}) | ||
} | ||
|
||
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] |
There was a problem hiding this comment.
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.
codex-rs/tui/src/updates.rs
Outdated
last_checked_at: chrono::DateTime<chrono::Utc>, | ||
} | ||
|
||
const VERSION_FILENAME: &str = "version.jsonl"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not just .json
?
codex-rs/tui/src/updates.rs
Outdated
let mut path = config.codex_home.clone(); | ||
path.push(VERSION_FILENAME); | ||
path |
There was a problem hiding this comment.
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?
let mut path = config.codex_home.clone(); | |
path.push(VERSION_FILENAME); | |
path | |
config.codex_home.join(VERSION_FILENAME).to_path_buf() |
codex-rs/tui/src/updates.rs
Outdated
} | ||
|
||
async fn check_for_update(version_file: &Path) -> anyhow::Result<()> { | ||
#[derive(serde::Deserialize, Debug, Clone)] |
There was a problem hiding this comment.
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.
codex-rs/tui/src/updates.rs
Outdated
latest_version: latest_tag_name | ||
.strip_prefix("rust-v") | ||
.ok_or_else(|| { | ||
anyhow::anyhow!("Failed to parse latest tag name '{}'", latest_tag_name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
anyhow::anyhow!("Failed to parse latest tag name '{}'", latest_tag_name) | |
anyhow::anyhow!("Failed to parse latest tag name '{latest_tag_name}'") |
codex-rs/tui/src/updates.rs
Outdated
|
||
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(); |
There was a problem hiding this comment.
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)
codex-rs/tui/src/updates.rs
Outdated
tag_name: String, | ||
} | ||
|
||
let resp = reqwest::Client::new() |
There was a problem hiding this comment.
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:
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;