mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
cli: add strict config to exec-server (#23719)
## Why PR #20559 added opt-in strict config parsing to the config-loading command surfaces, but `codex exec-server` was left out. That meant `codex exec-server --strict-config` was rejected even though the command can load config for remote registration, and local server startup had no way to fail fast on misspelled config keys. ## What Changed - Added `--strict-config` to `codex exec-server`. - Allowed root-level inheritance from `codex --strict-config exec-server`. - Validated config before local exec-server startup when strict mode is requested. - Reused the loaded strict-config-aware config for remote exec-server registration auth. - Added CLI coverage showing `codex exec-server --strict-config` rejects unknown config fields. ## Verification - `cargo test -p codex-cli` - New integration test: `strict_config_rejects_unknown_config_fields_for_exec_server` ## Documentation Any strict-config command list on developers.openai.com/codex should include `codex exec-server` with the other supported config-loading entry points.
This commit is contained in:
committed by
GitHub
Unverified
parent
fe7c069fe6
commit
d1e3d54192
+52
-20
@@ -478,6 +478,10 @@ struct AppServerCommand {
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
struct ExecServerCommand {
|
||||
/// Error out when config.toml contains fields that are not recognized by this version of Codex.
|
||||
#[arg(long = "strict-config", default_value_t = false)]
|
||||
strict_config: bool,
|
||||
|
||||
/// Transport endpoint URL. Supported values: `ws://IP:PORT` (default), `stdio`, `stdio://`.
|
||||
#[arg(long = "listen", value_name = "URL", conflicts_with = "remote")]
|
||||
listen: Option<String>,
|
||||
@@ -1376,11 +1380,13 @@ async fn cli_main(arg0_paths: Arg0DispatchPaths) -> anyhow::Result<()> {
|
||||
root_remote_auth_token_env.as_deref(),
|
||||
"exec-server",
|
||||
)?;
|
||||
let strict_config = cmd.strict_config || root_strict_config;
|
||||
run_exec_server_command(
|
||||
cmd,
|
||||
&arg0_paths,
|
||||
&root_config_overrides,
|
||||
interactive.config_profile.clone(),
|
||||
strict_config,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
@@ -1481,6 +1487,7 @@ async fn run_exec_server_command(
|
||||
arg0_paths: &Arg0DispatchPaths,
|
||||
root_config_overrides: &CliConfigOverrides,
|
||||
config_profile: Option<String>,
|
||||
strict_config: bool,
|
||||
) -> anyhow::Result<()> {
|
||||
let codex_self_exe = arg0_paths
|
||||
.codex_self_exe
|
||||
@@ -1494,12 +1501,10 @@ async fn run_exec_server_command(
|
||||
let environment_id = cmd
|
||||
.environment_id
|
||||
.ok_or_else(|| anyhow::anyhow!("--environment-id is required when --remote is set"))?;
|
||||
let auth_provider = load_exec_server_remote_auth_provider(
|
||||
root_config_overrides,
|
||||
config_profile,
|
||||
cmd.use_agent_identity_auth,
|
||||
)
|
||||
.await?;
|
||||
let config =
|
||||
load_exec_server_config(root_config_overrides, config_profile, strict_config).await?;
|
||||
let auth_provider =
|
||||
load_exec_server_remote_auth_provider(&config, cmd.use_agent_identity_auth).await?;
|
||||
let mut remote_config = codex_exec_server::RemoteEnvironmentConfig::new(
|
||||
base_url,
|
||||
environment_id,
|
||||
@@ -1509,23 +1514,29 @@ async fn run_exec_server_command(
|
||||
remote_config.name = name;
|
||||
}
|
||||
codex_exec_server::run_remote_environment(remote_config, runtime_paths).await?;
|
||||
return Ok(());
|
||||
Ok(())
|
||||
} else {
|
||||
if strict_config {
|
||||
// Local exec-server startup does not consume Config, but strict
|
||||
// mode should still reject unknown fields before opening a listener.
|
||||
let _validated_config =
|
||||
load_exec_server_config(root_config_overrides, config_profile, strict_config)
|
||||
.await?;
|
||||
}
|
||||
let listen_url = cmd
|
||||
.listen
|
||||
.as_deref()
|
||||
.unwrap_or(codex_exec_server::DEFAULT_LISTEN_URL);
|
||||
codex_exec_server::run_main(listen_url, runtime_paths)
|
||||
.await
|
||||
.map_err(anyhow::Error::from_boxed)
|
||||
}
|
||||
let listen_url = cmd
|
||||
.listen
|
||||
.as_deref()
|
||||
.unwrap_or(codex_exec_server::DEFAULT_LISTEN_URL);
|
||||
codex_exec_server::run_main(listen_url, runtime_paths)
|
||||
.await
|
||||
.map_err(anyhow::Error::from_boxed)
|
||||
}
|
||||
|
||||
async fn load_exec_server_remote_auth_provider(
|
||||
root_config_overrides: &CliConfigOverrides,
|
||||
config_profile: Option<String>,
|
||||
config: &codex_core::config::Config,
|
||||
use_agent_identity_auth: bool,
|
||||
) -> anyhow::Result<codex_api::SharedAuthProvider> {
|
||||
let config = load_exec_server_remote_config(root_config_overrides, config_profile).await?;
|
||||
if use_agent_identity_auth {
|
||||
let agent_identity_jwt = read_codex_access_token_from_env().ok_or_else(|| {
|
||||
anyhow::anyhow!("CODEX_ACCESS_TOKEN is required when --use-agent-identity-auth is set")
|
||||
@@ -1537,7 +1548,7 @@ async fn load_exec_server_remote_auth_provider(
|
||||
}
|
||||
|
||||
let auth = load_exec_server_remote_auth(
|
||||
&config,
|
||||
config,
|
||||
"remote exec-server registration requires ChatGPT authentication; run `codex login` first",
|
||||
)
|
||||
.await?;
|
||||
@@ -1551,9 +1562,10 @@ async fn load_exec_server_remote_auth_provider(
|
||||
Ok(codex_model_provider::auth_provider_from_auth(&auth))
|
||||
}
|
||||
|
||||
async fn load_exec_server_remote_config(
|
||||
async fn load_exec_server_config(
|
||||
root_config_overrides: &CliConfigOverrides,
|
||||
config_profile: Option<String>,
|
||||
strict_config: bool,
|
||||
) -> anyhow::Result<codex_core::config::Config> {
|
||||
let cli_kv_overrides = root_config_overrides
|
||||
.parse_overrides()
|
||||
@@ -1564,6 +1576,7 @@ async fn load_exec_server_remote_config(
|
||||
config_profile,
|
||||
..Default::default()
|
||||
})
|
||||
.strict_config(strict_config)
|
||||
.build()
|
||||
.await?)
|
||||
}
|
||||
@@ -1869,6 +1882,7 @@ fn unsupported_subcommand_name_for_strict_config(
|
||||
| Some(Subcommand::Exec(_))
|
||||
| Some(Subcommand::Review(_))
|
||||
| Some(Subcommand::McpServer(_))
|
||||
| Some(Subcommand::ExecServer(_))
|
||||
| Some(Subcommand::Resume(_))
|
||||
| Some(Subcommand::Fork(_))
|
||||
| Some(Subcommand::Doctor(_)) => None,
|
||||
@@ -1892,7 +1906,6 @@ fn unsupported_subcommand_name_for_strict_config(
|
||||
Some(Subcommand::Apply(_)) => Some("apply"),
|
||||
Some(Subcommand::ResponsesApiProxy(_)) => Some("responses-api-proxy"),
|
||||
Some(Subcommand::StdioToUds(_)) => Some("stdio-to-uds"),
|
||||
Some(Subcommand::ExecServer(_)) => Some("exec-server"),
|
||||
Some(Subcommand::Features(_)) => Some("features"),
|
||||
}
|
||||
}
|
||||
@@ -2897,6 +2910,25 @@ mod tests {
|
||||
..
|
||||
}))
|
||||
);
|
||||
|
||||
let cli = MultitoolCli::try_parse_from(["codex", "exec-server", "--strict-config"])
|
||||
.expect("parse");
|
||||
assert_matches!(
|
||||
cli.subcommand,
|
||||
Some(Subcommand::ExecServer(ExecServerCommand {
|
||||
strict_config: true,
|
||||
..
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn root_strict_config_is_supported_for_exec_server() {
|
||||
let cli = MultitoolCli::try_parse_from(["codex", "--strict-config", "exec-server"])
|
||||
.expect("parse");
|
||||
|
||||
reject_root_strict_config_for_subcommand(cli.interactive.strict_config, &cli.subcommand)
|
||||
.expect("exec-server should support root --strict-config");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use predicates::str::contains;
|
||||
use tempfile::TempDir;
|
||||
|
||||
fn codex_command(codex_home: &Path) -> Result<assert_cmd::Command> {
|
||||
let mut cmd = assert_cmd::Command::new(codex_utils_cargo_bin::cargo_bin("codex")?);
|
||||
cmd.env("CODEX_HOME", codex_home);
|
||||
Ok(cmd)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strict_config_rejects_unknown_config_fields_for_exec_server() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
std::fs::write(
|
||||
codex_home.path().join("config.toml"),
|
||||
r#"
|
||||
foo = "bar"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let mut cmd = codex_command(codex_home.path())?;
|
||||
cmd.args([
|
||||
"exec-server",
|
||||
"--strict-config",
|
||||
"--listen",
|
||||
"http://127.0.0.1:0",
|
||||
])
|
||||
.assert()
|
||||
.failure()
|
||||
.stderr(contains("unknown configuration field"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user