Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions codex-rs/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

77 changes: 62 additions & 15 deletions codex-rs/cli/src/doctor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ use codex_core::config::ConfigBuilder;
use codex_core::config::ConfigOverrides;
use codex_core::config::find_codex_home;
use codex_features::FEATURES;
use codex_install_context::CodexPackageLayout;
use codex_install_context::InstallContext;
use codex_install_context::InstallMethod;
use codex_install_context::StandalonePlatform;
use codex_login::AuthDotJson;
use codex_login::AuthManager;
Expand Down Expand Up @@ -812,7 +814,10 @@ fn installation_check(show_details: bool) -> DoctorCheck {

fn doctor_install_context(current_exe: Option<&Path>) -> InstallContext {
if inherited_managed_env_for_cargo_binary(current_exe) {
InstallContext::Other
InstallContext {
method: InstallMethod::Other,
package_layout: None,
}
} else {
InstallContext::current().clone()
}
Expand Down Expand Up @@ -843,8 +848,8 @@ fn inherited_managed_env_for_cargo_binary(current_exe: Option<&Path>) -> bool {
}

fn describe_install_context(context: &InstallContext) -> String {
match context {
InstallContext::Standalone {
match &context.method {
InstallMethod::Standalone {
release_dir,
resources_dir,
platform,
Expand All @@ -853,22 +858,63 @@ fn describe_install_context(context: &InstallContext) -> String {
StandalonePlatform::Unix => "unix",
StandalonePlatform::Windows => "windows",
};
let resources = resources_dir
.as_ref()
.map(|path| path.display().to_string())
.unwrap_or_else(|| "none".to_string());
match &context.package_layout {
Some(package_layout) => {
let resources = display_optional_path(package_layout.resources_dir.as_deref());
let path = display_optional_path(package_layout.path_dir.as_deref());
format!(
"standalone ({platform}, package {}, bin {}, resources {resources}, path {path})",
package_layout.package_dir.display(),
package_layout.bin_dir.display()
)
}
None => {
let resources = display_optional_path(resources_dir.as_deref());
format!(
"standalone ({platform}, release {}, resources {resources})",
release_dir.display()
)
}
}
}
InstallMethod::Npm => {
describe_method_with_package_layout("npm", context.package_layout.as_ref())
}
InstallMethod::Bun => {
describe_method_with_package_layout("bun", context.package_layout.as_ref())
}
InstallMethod::Brew => {
describe_method_with_package_layout("brew", context.package_layout.as_ref())
}
InstallMethod::Other => {
describe_method_with_package_layout("other", context.package_layout.as_ref())
}
}
}

fn describe_method_with_package_layout(
method: &str,
package_layout: Option<&CodexPackageLayout>,
) -> String {
match package_layout {
Some(package_layout) => {
let resources = display_optional_path(package_layout.resources_dir.as_deref());
let path = display_optional_path(package_layout.path_dir.as_deref());
format!(
"standalone ({platform}, release {}, resources {resources})",
release_dir.display()
"{method} (package {}, bin {}, resources {resources}, path {path})",
package_layout.package_dir.display(),
package_layout.bin_dir.display()
)
}
InstallContext::Npm => "npm".to_string(),
InstallContext::Bun => "bun".to_string(),
InstallContext::Brew => "brew".to_string(),
InstallContext::Other => "other".to_string(),
None => method.to_string(),
}
}

fn display_optional_path(path: Option<&Path>) -> String {
path.map(|path| path.display().to_string())
.unwrap_or_else(|| "none".to_string())
}

#[derive(Debug, PartialEq, Eq)]
enum NpmRootCheck {
Match {
Expand Down Expand Up @@ -2791,13 +2837,14 @@ fn path_readiness(details: &mut Vec<String>, label: &str, path: &Path) {
}

fn standalone_release_cache_details(details: &mut Vec<String>) {
let InstallContext::Standalone { release_dir, .. } = InstallContext::current() else {
let context = InstallContext::current();
let InstallMethod::Standalone { release_dir, .. } = &context.method else {
return;
};
let Some(releases_dir) = release_dir.parent() else {
return;
};
let Ok(entries) = std::fs::read_dir(releases_dir) else {
let Ok(entries) = std::fs::read_dir(&releases_dir) else {
return;
};
let release_count = entries.filter_map(Result::ok).count();
Expand Down
44 changes: 29 additions & 15 deletions codex-rs/cli/src/doctor/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
//! Runtime diagnostics answer provenance questions that are hard to infer from
//! user reports: which binary is running, which install channel it resembles,
//! which platform it targets, and whether the search command comes from bundled
//! standalone resources or from PATH.
//! package files or from PATH.

use std::env;
use std::process::Command;

use codex_install_context::InstallContext;
use codex_install_context::InstallMethod;

use super::CheckStatus;
use super::DoctorCheck;
Expand Down Expand Up @@ -49,9 +50,10 @@ pub(super) fn runtime_check() -> DoctorCheck {

/// Verifies that the search command selected by the install context is usable.
///
/// Standalone installs should point at a bundled ripgrep binary, while local or
/// package-managed installs usually resolve rg from PATH. A warning here means
/// features that depend on file search may degrade even when the CLI launches.
/// Package-layout installs should point at a bundled ripgrep binary, while local
/// installs without that layout usually resolve rg from PATH. A warning here
/// means features that depend on file search may degrade even when the CLI
/// launches.
pub(super) fn search_check() -> DoctorCheck {
let current_exe = env::current_exe().ok();
let install_context = doctor_install_context(current_exe.as_deref());
Expand Down Expand Up @@ -109,28 +111,40 @@ pub(super) fn search_check() -> DoctorCheck {
};
let mut check = DoctorCheck::new("runtime.search", "search", status, summary).details(details);
if status != CheckStatus::Ok {
check = check.remediation("Install ripgrep or repair the bundled standalone resources.");
check = check.remediation("Install ripgrep or repair the bundled Codex package.");
}
check
}

fn install_method_name(context: &InstallContext) -> &'static str {
match context {
InstallContext::Standalone { .. } => "standalone",
InstallContext::Npm => "npm",
InstallContext::Bun => "bun",
InstallContext::Brew => "brew",
InstallContext::Other => "local build",
match &context.method {
InstallMethod::Standalone { .. } => "standalone",
InstallMethod::Npm => "npm",
InstallMethod::Bun => "bun",
InstallMethod::Brew => "brew",
InstallMethod::Other => "local build",
}
}

fn search_provider(context: &InstallContext) -> &'static str {
match context {
InstallContext::Standalone {
let rg_command = context.rg_command();
let from_package_layout = context
.package_layout
.as_ref()
.and_then(|package_layout| package_layout.path_dir.as_ref())
.is_some_and(|path_dir| rg_command.starts_with(path_dir));
let from_legacy_standalone = matches!(
&context.method,
InstallMethod::Standalone {
resources_dir: Some(resources_dir),
..
} if context.rg_command().starts_with(resources_dir) => "bundled",
_ => "system",
} if rg_command.starts_with(resources_dir)
);

if from_package_layout || from_legacy_standalone {
"bundled"
} else {
"system"
}
}

Expand Down
35 changes: 21 additions & 14 deletions codex-rs/cli/src/doctor/updates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use std::path::Path;

use codex_core::config::Config;
use codex_install_context::InstallContext;
use codex_install_context::InstallMethod;
use serde::Deserialize;

use super::CheckStatus;
Expand Down Expand Up @@ -129,22 +130,22 @@ fn push_cached_version_details(details: &mut Vec<String>, version_file: &Path) {
}

fn update_action_label(context: &InstallContext) -> &'static str {
match context {
InstallContext::Npm => "npm install -g @openai/codex",
InstallContext::Bun => "bun install -g @openai/codex",
InstallContext::Brew => "brew upgrade --cask codex",
InstallContext::Standalone { .. } => "standalone installer",
InstallContext::Other => "manual or unknown",
match &context.method {
InstallMethod::Npm => "npm install -g @openai/codex",
InstallMethod::Bun => "bun install -g @openai/codex",
InstallMethod::Brew => "brew upgrade --cask codex",
InstallMethod::Standalone { .. } => "standalone installer",
InstallMethod::Other => "manual or unknown",
}
}

fn fetch_latest_version(context: &InstallContext) -> Result<String, String> {
match context {
InstallContext::Brew => fetch_homebrew_cask_version(),
InstallContext::Npm
| InstallContext::Bun
| InstallContext::Standalone { .. }
| InstallContext::Other => fetch_latest_github_release_version(),
match &context.method {
InstallMethod::Brew => fetch_homebrew_cask_version(),
InstallMethod::Npm
| InstallMethod::Bun
| InstallMethod::Standalone { .. }
| InstallMethod::Other => fetch_latest_github_release_version(),
}
}

Expand Down Expand Up @@ -216,11 +217,17 @@ mod tests {
#[test]
fn update_action_labels_install_contexts() {
assert_eq!(
update_action_label(&InstallContext::Npm),
update_action_label(&InstallContext {
method: InstallMethod::Npm,
package_layout: None,
}),
"npm install -g @openai/codex"
);
assert_eq!(
update_action_label(&InstallContext::Other),
update_action_label(&InstallContext {
method: InstallMethod::Other,
package_layout: None,
}),
"manual or unknown"
);
}
Expand Down
1 change: 1 addition & 0 deletions codex-rs/install-context/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ doctest = false
workspace = true

[dependencies]
codex-utils-absolute-path = { workspace = true }
codex-utils-home-dir = { workspace = true }

[dev-dependencies]
Expand Down
Loading
Loading