Report the exec-server release version in environment info (#42270)

## Why

Clients need the executor's release version to make version-based compatibility decisions.

## What changed

- Add `executorVersion` to `EnvironmentInfo` and return it from both `initialize` and `environment/info`.
- Resolve the package release version at process startup so the advertised value remains stable for the server's lifetime.
- Use `0.0.0` when the version is unknown or omitted by a legacy executor.

## Testing

- Cover packaged and source-build initialization, startup caching, protocol serialization, and client-side metadata caching.

GitOrigin-RevId: a0c7f0b701a8e7e50345cd5fb1b5c5d2d38ff3b1
This commit is contained in:
felixxia-oai
2026-09-02 12:07:32 +00:00
committed by copyberry
parent a0dcfe2ada
commit 8d32abcd01
15 changed files with 131 additions and 28 deletions
+1
View File
@@ -32,6 +32,7 @@ codex-app-server-daemon = { workspace = true }
codex-app-server-protocol = { workspace = true }
codex-app-server-test-client = { workspace = true }
codex-arg0 = { workspace = true }
codex-build-info = { workspace = true }
codex-api = { workspace = true }
codex-chatgpt = { workspace = true }
codex-cloud-config = { workspace = true }
+1
View File
@@ -1036,6 +1036,7 @@ fn stage_str(stage: Stage) -> &'static str {
}
fn main() -> anyhow::Result<()> {
codex_build_info::initialize!();
let remote_control_disabled = codex_app_server::take_remote_control_disabled_env();
arg0_dispatch_or_else(move |arg0_paths: Arg0DispatchPaths| async move {
cli_main(arg0_paths, remote_control_disabled).await?;
+21 -1
View File
@@ -19,6 +19,7 @@ use std::time::Instant;
use anyhow::Context;
use anyhow::Result;
use codex_exec_server::EnvironmentInfo;
use codex_exec_server::ExecParams;
use codex_exec_server::ExecServerClient;
use codex_exec_server::NoiseChannelIdentity;
@@ -32,6 +33,7 @@ use futures::SinkExt;
use futures::StreamExt;
use predicates::prelude::PredicateBooleanExt;
use predicates::str::contains;
use pretty_assertions::assert_eq;
use tempfile::TempDir;
use tokio::io::AsyncBufReadExt;
use tokio::io::AsyncReadExt;
@@ -175,7 +177,15 @@ metrics_exporter = {{ otlp-http = {{ endpoint = "{collector_url}/v1/metrics", pr
"#
),
)?;
let mut command = tokio::process::Command::new(codex_utils_cargo_bin::cargo_bin("codex")?);
let package = TempDir::new()?;
let bin_dir = package.path().join("bin");
std::fs::create_dir(&bin_dir)?;
let executable = bin_dir.join(format!("codex{}", std::env::consts::EXE_SUFFIX));
std::fs::copy(codex_utils_cargo_bin::cargo_bin("codex")?, &executable)?;
let manifest = package.path().join("codex-package.json");
std::fs::write(&manifest, r#"{"version":"1.2.3-alpha.4"}"#)?;
let mut command = tokio::process::Command::new(executable);
command
.env("CODEX_HOME", codex_home.path())
.env("CODEX_API_KEY", "test-api-key")
@@ -205,6 +215,8 @@ metrics_exporter = {{ otlp-http = {{ endpoint = "{collector_url}/v1/metrics", pr
.ok_or_else(|| anyhow::anyhow!("remote exec-server stdin was not piped"))?;
let environment_websocket = accept_parent_lifetime_websocket(&listener, TEST_TIMEOUT).await?;
// Remote startup must capture the version before registration, not on the first initialize.
std::fs::write(&manifest, r#"{"version":"9.9.9"}"#)?;
let executor_public_key = registered_parent_lifetime_executor_public_key(&registry).await?;
let harness_args = NoiseRendezvousConnectArgs {
bundle: NoiseRendezvousConnectBundle {
@@ -232,6 +244,14 @@ metrics_exporter = {{ otlp-http = {{ endpoint = "{collector_url}/v1/metrics", pr
.await
.context("remote harness did not connect")???;
let expected_info = EnvironmentInfo {
executor_version: "1.2.3-alpha.4".to_string(),
..EnvironmentInfo::local()
};
assert_eq!(client.environment_info().await?, expected_info);
std::fs::remove_file(&manifest)?;
assert_eq!(client.force_environment_info().await?, expected_info);
#[cfg(windows)]
let argv = vec![
"cmd.exe",