Files
codex/prs/bolinfest/study/PR-1764-study.md
2025-09-02 15:17:45 -07:00

6.5 KiB
Raw Blame History

DOs

  • Alpha-Sort Dependencies: Keep Cargo.toml dependencies alphabetized for readability and easy diffs.
[dependencies]
chrono = { version = "0.4", features = ["serde"] }
reqwest = { version = "0.12", features = ["json"] }
serde = { version = "1", features = ["derive"] }
serde_json = { version = "1", features = ["preserve_order"] }
  • Gate Update Logic: Compile update-check code for release (and tests) only.
// at top of updates.rs
#![cfg(any(not(debug_assertions), test))]
  • Fetch Updates in Background: Dont block TUI startup; refresh cached version asynchronously.
let version_file = version_filepath(config);
tokio::spawn(async move {
    if let Err(e) = check_for_update(&version_file).await {
        tracing::error!("Failed to update version: {e}");
    }
});
  • Recommend Updates Safely: Prefer npm when launched by npm; otherwise detect Homebrew install on macOS; else link to releases.
#[allow(clippy::print_stderr)]
#[cfg(not(debug_assertions))]
if let Some(latest) = updates::get_upgrade_version(&config) {
    let current = env!("CARGO_PKG_VERSION");
    let exe = std::env::current_exe()?;
    let managed_by_npm = std::env::var_os("CODEX_MANAGED_BY_NPM").is_some();

    eprintln!("{} {current} -> {latest}.", "✨⬆️ Update available!".bold().cyan());

    if managed_by_npm {
        eprintln!("Run {} to update.", "npm install -g @openai/codex@latest".cyan().on_black());
    } else if cfg!(target_os = "macos") && exe.starts_with("/opt/homebrew") {
        eprintln!("Run {} to update.", "brew upgrade codex".cyan().on_black());
    } else {
        eprintln!("See {} for the latest releases and installation options.",
            "https://github.com/openai/codex/releases/latest".cyan().on_black());
    }
    eprintln!("");
}
  • Join Paths Cleanly: Build paths with join, not manual push/clone.
fn version_filepath(config: &Config) -> std::path::PathBuf {
    config.codex_home.join("version.json")
}
  • Use Top-Level Serde Types: Define models once; derive Serialize/Deserialize; use chrono DateTime.
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Debug, Clone)]
struct VersionInfo {
    latest_version: String,
    last_checked_at: DateTime<Utc>,
}

#[derive(Deserialize, Debug, Clone)]
struct ReleaseInfo {
    tag_name: String,
}
  • Destructure JSON Results: Pull out only what you need.
let ReleaseInfo { tag_name: latest_tag_name } = reqwest::Client::new()
    .get("https://api.github.com/repos/openai/codex/releases/latest")
    .header("User-Agent", format!("codex/{} (+https://github.com/openai/codex)", env!("CARGO_PKG_VERSION")))
    .send().await?
    .error_for_status()?
    .json::<ReleaseInfo>().await?;
  • Propagate Errors: Dont swallow filesystem errors; use ?.
if let Some(parent) = version_file.parent() {
    tokio::fs::create_dir_all(parent).await?;
}
tokio::fs::write(version_file, format!("{}\n", serde_json::to_string(&info)?)).await?;
  • Inline Variables in format!: Prefer inline braces for clarity and lint happiness.
return Err(anyhow::anyhow!("Failed to parse latest tag name '{latest_tag_name}'"));
  • Compare Strict Semver (no pre-release): Parse major.minor.patch only; add tests for edge cases.
fn parse_version(v: &str) -> Option<(u64, u64, u64)> {
    let mut it = v.trim().split('.');
    Some((it.next()?.parse().ok()?, it.next()?.parse().ok()?, it.next()?.parse().ok()?))
}

fn is_newer(latest: &str, current: &str) -> Option<bool> {
    match (parse_version(latest), parse_version(current)) {
        (Some(l), Some(c)) => Some(l > c),
        _ => None,
    }
}

#[test]
fn prerelease_not_considered_newer() {
    assert_eq!(is_newer("0.11.0-beta.1", "0.11.0"), None);
}

#[test]
fn whitespace_ignored() {
    assert_eq!(is_newer(" 1.2.3 ", "1.2.2"), Some(true));
}
  • Send a User-Agent: Include a helpful UA for GitHub API calls.
.header("User-Agent", format!("codex/{} (+https://github.com/openai/codex)", env!("CARGO_PKG_VERSION")))

DONTs

  • Dont Block Startup on Network: Avoid awaiting the HTTP call at boot.
// Avoid:
check_for_update(&version_file).await?; // blocks UI

// Prefer:
tokio::spawn(async move { let _ = check_for_update(&version_file).await; });
  • Dont Rely on Fragile Path Heuristics: Avoid assuming /usr/local means Homebrew.
// Avoid:
else if exe.starts_with("/usr/local") { /* assume Homebrew */ }

// Prefer a solid signal:
else if cfg!(target_os = "macos") && exe.starts_with("/opt/homebrew") { /* Homebrew */ }
// Or gate via a build-time flag set by the formula:
else if option_env!("CODEX_HOMEBREW_BUILD").is_some() { /* Homebrew */ }
  • Dont Misname Functions: Avoid names like update_version that imply self-updating the CLI.
// Avoid:
async fn update_version(...) -> Result<()> { /* writes metadata */ }

// Prefer:
async fn check_for_update(...) -> Result<()> { /* writes metadata */ }
  • Dont Use .jsonl for Single Objects: Save a single JSON object as .json, not .jsonl.
// Avoid:
const VERSION_FILENAME: &str = "version.jsonl";

// Prefer:
const VERSION_FILENAME: &str = "version.json";
  • Dont Nest Serde Models in Functions: Define them at module scope for reuse and clarity.
// Avoid:
async fn check_for_update(...) {
    #[derive(serde::Deserialize)]
    struct ReleaseInfo { tag_name: String }
    /* ... */
}

// Prefer: top-level `ReleaseInfo`
  • Dont Swallow Errors with ok(): Propagate failures to logs/callers.
// Avoid:
tokio::fs::create_dir_all(parent).await.ok();
tokio::fs::write(version_file, json_line).await.ok();

// Prefer:
tokio::fs::create_dir_all(parent).await?;
tokio::fs::write(version_file, json_line).await?;
  • Dont Treat Pre-Releases as Upgrades: Ensure 0.11.0-beta.1 is not considered newer than 0.11.0.
assert_eq!(is_newer("0.11.0-beta.1", "0.11.0"), None);
  • Dont Hardcode Update Commands Blindly: Recommend npm only when launched by npm; otherwise prefer strong signals or a link.
// Avoid:
eprintln!("Run npm install -g @openai/codex@latest");

// Prefer:
if std::env::var_os("CODEX_MANAGED_BY_NPM").is_some() { /* npm */ } else { /* brew or link */ }
  • Dont Sprawl Update UI in lib.rs: Keep the boot message thin; push logic into updates.rs.
// In lib.rs, keep it minimal:
if let Some(latest) = updates::get_upgrade_version(&config) { /* print short message */ }