mirror of
https://github.com/openai/codex.git
synced 2026-10-29 19:08:43 +00:00
Add codex doctor --network-only for focused network diagnostics (#57606)
## What changed Limit `--network-only` reports to effective configuration, network environment, provider HTTP reachability, and Responses WebSocket checks, skipping unrelated diagnostics such as database and MCP scans. Preserve configuration warnings and report configuration load timing. If configuration loading fails, report the redacted configuration error and stop without probing guessed network targets. ## Testing Add integration tests for check selection, recoverable configuration warnings, and configuration failures that send no probe requests or expose configuration values. Add snapshots for human-readable success and failure reports. GitOrigin-RevId: 997616b6802813df751725f9538541c65bad7d71
This commit is contained in:
+53
-38
@@ -154,8 +154,9 @@ const NARROW_TERMINAL_ROWS: u16 = 24;
|
||||
|
||||
/// Options for building a local Codex diagnostic report.
|
||||
///
|
||||
/// The command always runs the full diagnostic set. Human output includes
|
||||
/// detailed diagnostics by default; --summary keeps the terminal output compact.
|
||||
/// The command runs the full diagnostic set unless `--network-only` is selected.
|
||||
/// Human output includes detailed diagnostics by default; --summary keeps the terminal output
|
||||
/// compact.
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct DoctorCommand {
|
||||
/// Internal isolated filesystem probe; exits before loading configuration.
|
||||
@@ -166,6 +167,10 @@ pub struct DoctorCommand {
|
||||
#[arg(long, default_value_t = false)]
|
||||
json: bool,
|
||||
|
||||
/// Run config, network environment, provider HTTP, and Responses WebSocket checks.
|
||||
#[arg(long, default_value_t = false)]
|
||||
network_only: bool,
|
||||
|
||||
/// Limit database integrity scans when collecting a feedback attachment.
|
||||
#[arg(long, hide = true, default_value_t = false)]
|
||||
feedback: bool,
|
||||
@@ -358,6 +363,12 @@ async fn build_report(
|
||||
arg0_paths: &Arg0DispatchPaths,
|
||||
) -> DoctorReport {
|
||||
let progress = doctor_progress(command.json);
|
||||
if command.network_only {
|
||||
let checks =
|
||||
network::checks(root_config_overrides, interactive, arg0_paths, &progress).await;
|
||||
progress.settle();
|
||||
return report_from_checks(checks);
|
||||
}
|
||||
let mut checks = Vec::new();
|
||||
checks.push(run_sync_check("system", progress.clone(), system_check));
|
||||
checks.push(run_async_check("endpoint protection", progress.clone(), security::check()).await);
|
||||
@@ -525,42 +536,7 @@ async fn build_report(
|
||||
state_check,
|
||||
reachability_check,
|
||||
) = tokio::join!(
|
||||
async {
|
||||
run_sync_check("config", progress.clone(), || {
|
||||
let check = DoctorCheck::new(
|
||||
"config.load",
|
||||
"config",
|
||||
CheckStatus::Fail,
|
||||
"config could not be loaded",
|
||||
)
|
||||
.remediation("Fix the reported config error, then rerun codex doctor.");
|
||||
// Error messages can echo config values. Report only typed metadata,
|
||||
// including errors wrapped by io::Error, whose source skips the wrapper.
|
||||
let config_error = err.chain().find_map(|cause| {
|
||||
cause.downcast_ref::<ConfigLoadError>().or_else(|| {
|
||||
cause
|
||||
.downcast_ref::<std::io::Error>()?
|
||||
.get_ref()?
|
||||
.downcast_ref::<ConfigLoadError>()
|
||||
})
|
||||
});
|
||||
if let Some(error) = config_error {
|
||||
let error = error.config_error();
|
||||
return check
|
||||
.detail("error: invalid configuration")
|
||||
.detail(format!("file: {}", error.path.display()))
|
||||
.detail(format!("line: {}", error.range.start.line))
|
||||
.detail(format!("column: {}", error.range.start.column));
|
||||
}
|
||||
let io_error = err
|
||||
.chain()
|
||||
.find_map(|cause| cause.downcast_ref::<std::io::Error>());
|
||||
match io_error {
|
||||
Some(error) => check.detail(format!("error: {}", error.kind())),
|
||||
None => check.detail("error: configuration load failed"),
|
||||
}
|
||||
})
|
||||
},
|
||||
async { run_sync_check("config", progress.clone(), || config_load_failure(err)) },
|
||||
async {
|
||||
run_sync_check("network", progress.clone(), || {
|
||||
network::check(/*config*/ None)
|
||||
@@ -603,6 +579,10 @@ async fn build_report(
|
||||
|
||||
progress.settle();
|
||||
|
||||
report_from_checks(checks)
|
||||
}
|
||||
|
||||
fn report_from_checks(checks: Vec<DoctorCheck>) -> DoctorReport {
|
||||
let overall_status = overall_status(&checks);
|
||||
DoctorReport {
|
||||
schema_version: 1,
|
||||
@@ -613,6 +593,41 @@ async fn build_report(
|
||||
}
|
||||
}
|
||||
|
||||
fn config_load_failure(err: &anyhow::Error) -> DoctorCheck {
|
||||
let check = DoctorCheck::new(
|
||||
"config.load",
|
||||
"config",
|
||||
CheckStatus::Fail,
|
||||
"config could not be loaded",
|
||||
)
|
||||
.remediation("Fix the reported config error, then rerun codex doctor.");
|
||||
// Error messages can echo config values. Report only typed metadata,
|
||||
// including errors wrapped by io::Error, whose source skips the wrapper.
|
||||
let config_error = err.chain().find_map(|cause| {
|
||||
cause.downcast_ref::<ConfigLoadError>().or_else(|| {
|
||||
cause
|
||||
.downcast_ref::<std::io::Error>()?
|
||||
.get_ref()?
|
||||
.downcast_ref::<ConfigLoadError>()
|
||||
})
|
||||
});
|
||||
if let Some(error) = config_error {
|
||||
let error = error.config_error();
|
||||
return check
|
||||
.detail("error: invalid configuration")
|
||||
.detail(format!("file: {}", error.path.display()))
|
||||
.detail(format!("line: {}", error.range.start.line))
|
||||
.detail(format!("column: {}", error.range.start.column));
|
||||
}
|
||||
let io_error = err
|
||||
.chain()
|
||||
.find_map(|cause| cause.downcast_ref::<std::io::Error>());
|
||||
match io_error {
|
||||
Some(error) => check.detail(format!("error: {}", error.kind())),
|
||||
None => check.detail("error: configuration load failed"),
|
||||
}
|
||||
}
|
||||
|
||||
async fn load_config(
|
||||
mut root_config_overrides: CliConfigOverrides,
|
||||
interactive: &TuiCli,
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
|
||||
use codex_arg0::Arg0DispatchPaths;
|
||||
use codex_core::config::Config;
|
||||
#[cfg(target_os = "macos")]
|
||||
use codex_http_client::MacosSystemProxyConfiguration;
|
||||
@@ -11,14 +14,67 @@ use codex_http_client::RouteFailureClass;
|
||||
#[cfg(target_os = "macos")]
|
||||
use codex_http_client::macos_system_proxy_configuration;
|
||||
use codex_login::default_client::create_client_without_request_logging;
|
||||
use codex_tui::Cli as TuiCli;
|
||||
use codex_utils_cli::CliConfigOverrides;
|
||||
use http::HeaderMap;
|
||||
use http::Method;
|
||||
|
||||
use super::CheckStatus;
|
||||
use super::DoctorCheck;
|
||||
use super::DoctorProgress;
|
||||
use super::push_proxy_env_details;
|
||||
use super::read_probe_file;
|
||||
|
||||
pub(super) async fn checks(
|
||||
overrides: CliConfigOverrides,
|
||||
interactive: &TuiCli,
|
||||
arg0_paths: &Arg0DispatchPaths,
|
||||
progress: &Arc<dyn DoctorProgress>,
|
||||
) -> Vec<DoctorCheck> {
|
||||
progress.begin("config");
|
||||
let config_started = Instant::now();
|
||||
let config = match super::load_config(overrides, interactive, arg0_paths).await {
|
||||
Ok(config) => config,
|
||||
Err(error) => {
|
||||
let check = super::config_load_failure(&error)
|
||||
.detail("network diagnostics: effective network configuration is unavailable");
|
||||
progress.finish("config", check.status);
|
||||
return vec![check];
|
||||
}
|
||||
};
|
||||
let config_duration = config_started.elapsed();
|
||||
let mut config_check = super::config_check(&config).detail(format!(
|
||||
"configuration load ms: {}",
|
||||
config_duration.as_millis()
|
||||
));
|
||||
config_check.duration_ms = config_duration.as_millis().try_into().unwrap_or(u64::MAX);
|
||||
progress.finish("config", config_check.status);
|
||||
let auth_manager = codex_login::AuthManager::shared_from_config(
|
||||
&config, /*enable_codex_api_key_env*/ true,
|
||||
)
|
||||
.await
|
||||
.ok();
|
||||
let plan = super::provider_reachability_plan(&config);
|
||||
#[cfg(target_os = "macos")]
|
||||
let url = plan.endpoints.first().map(|endpoint| endpoint.url.clone());
|
||||
let (network, websocket, reachability) = tokio::join!(
|
||||
async { super::run_sync_check("network", progress.clone(), || check(Some(&config))) },
|
||||
super::run_async_check(
|
||||
"websocket",
|
||||
progress.clone(),
|
||||
super::websocket_reachability_check(&config, auth_manager)
|
||||
),
|
||||
super::run_async_check(
|
||||
"provider reachability",
|
||||
progress.clone(),
|
||||
super::provider_reachability_check(plan)
|
||||
),
|
||||
);
|
||||
#[cfg(target_os = "macos")]
|
||||
let reachability = with_system_proxy_remediation(reachability, &config, url.as_deref());
|
||||
vec![config_check, network, websocket, reachability]
|
||||
}
|
||||
|
||||
pub(super) fn check(config: Option<&Config>) -> DoctorCheck {
|
||||
let mut details = Vec::new();
|
||||
push_proxy_env_details(&mut details);
|
||||
|
||||
Reference in New Issue
Block a user