mirror of
https://github.com/openai/codex.git
synced 2026-10-21 12:10:06 +00:00
Expose the Codex version to commands and turn metadata (#42395)
## What changed - Set `CODEX_VERSION` to the running package version in user shell and unified exec environments, overriding stale shell-policy or snapshot values. - Add the reserved `codex_version` field to MCP turn metadata. ## Testing - Cover version propagation through shell policies, shell snapshots, unified exec, and MCP metadata. GitOrigin-RevId: 7846c70d668176530b1f467293b749dc9ddd79a0
This commit is contained in:
@@ -13,6 +13,8 @@ use std::collections::HashMap;
|
||||
pub use codex_protocol::shell_environment::CODEX_SESSION_ID_ENV_VAR;
|
||||
pub use codex_protocol::shell_environment::CODEX_THREAD_ID_ENV_VAR;
|
||||
|
||||
pub(crate) const CODEX_VERSION_ENV_VAR: &str = "CODEX_VERSION";
|
||||
|
||||
/// Informational name of the active permission profile. Child processes can
|
||||
/// overwrite this value, so it must not be treated as proof of enforcement.
|
||||
pub const CODEX_PERMISSION_PROFILE_ENV_VAR: &str = "CODEX_PERMISSION_PROFILE";
|
||||
@@ -35,9 +37,16 @@ pub fn create_env(
|
||||
shell_environment::create_env(policy, thread_id.as_deref())
|
||||
}
|
||||
|
||||
/// Exposes the shared root-session identity to model-reachable shell commands.
|
||||
pub(crate) fn inject_session_id_env(env: &mut HashMap<String, String>, session_id: SessionId) {
|
||||
/// Exposes the shared root-session identity and harness version to shell commands.
|
||||
pub(crate) fn inject_session_env(env: &mut HashMap<String, String>, session_id: SessionId) {
|
||||
env.insert(CODEX_SESSION_ID_ENV_VAR.to_string(), session_id.to_string());
|
||||
if cfg!(windows) {
|
||||
env.retain(|key, _| !key.eq_ignore_ascii_case(CODEX_VERSION_ENV_VAR));
|
||||
}
|
||||
env.insert(
|
||||
CODEX_VERSION_ENV_VAR.to_string(),
|
||||
env!("CARGO_PKG_VERSION").to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Injects the selected named permission profile into a shell tool's environment.
|
||||
|
||||
@@ -15,7 +15,7 @@ use crate::exec::StdoutStream;
|
||||
use crate::exec::execute_exec_request;
|
||||
use crate::exec_env::create_env;
|
||||
use crate::exec_env::inject_apply_patch_env;
|
||||
use crate::exec_env::inject_session_id_env;
|
||||
use crate::exec_env::inject_session_env;
|
||||
use crate::sandboxing::ExecRequest;
|
||||
use crate::session::TurnInput;
|
||||
use crate::session::turn_context::TurnContext;
|
||||
@@ -166,7 +166,7 @@ pub(crate) async fn execute_user_shell_command(
|
||||
let shell_snapshot_location = turn_environment.shell_snapshot(&cwd);
|
||||
let shell_environment_policy = turn_environment.shell_environment_policy();
|
||||
let mut exec_env_map = create_env(shell_environment_policy, Some(session.thread_id));
|
||||
inject_session_id_env(&mut exec_env_map, session.session_id());
|
||||
inject_session_env(&mut exec_env_map, session.session_id());
|
||||
inject_apply_patch_env(&mut exec_env_map, &turn_context.config.features);
|
||||
if exec_env_map.contains_key(PROXY_ACTIVE_ENV_KEY) {
|
||||
strip_managed_proxy_env(&mut exec_env_map);
|
||||
|
||||
@@ -7,6 +7,7 @@ small and focused and reuses the orchestrator for approvals + sandbox + retry.
|
||||
use crate::exec_env::CODEX_PERMISSION_PROFILE_ENV_VAR;
|
||||
use crate::exec_env::CODEX_SESSION_ID_ENV_VAR;
|
||||
use crate::exec_env::CODEX_THREAD_ID_ENV_VAR;
|
||||
use crate::exec_env::CODEX_VERSION_ENV_VAR;
|
||||
use crate::sandboxing::SandboxPermissions;
|
||||
use crate::shell::Shell;
|
||||
use crate::shell::ShellType;
|
||||
@@ -264,6 +265,7 @@ pub(crate) fn maybe_wrap_shell_lc_with_snapshot(
|
||||
for key in [
|
||||
CODEX_SESSION_ID_ENV_VAR,
|
||||
CODEX_THREAD_ID_ENV_VAR,
|
||||
CODEX_VERSION_ENV_VAR,
|
||||
CODEX_PERMISSION_PROFILE_ENV_VAR,
|
||||
CODEX_APPLY_PATCH_PRESERVE_LINE_ENDINGS_ENV_VAR,
|
||||
PLUGIN_METRICS_OUTPUT_ENV_VAR,
|
||||
|
||||
@@ -494,12 +494,12 @@ fn maybe_wrap_shell_lc_with_snapshot_restores_explicit_override_precedence() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn maybe_wrap_shell_lc_with_snapshot_restores_codex_thread_id_from_env() {
|
||||
fn maybe_wrap_shell_lc_with_snapshot_restores_codex_metadata_from_env() {
|
||||
let dir = tempdir().expect("create temp dir");
|
||||
let snapshot_path = dir.path().join("snapshot.sh");
|
||||
std::fs::write(
|
||||
&snapshot_path,
|
||||
"# Snapshot file\nexport CODEX_THREAD_ID='parent-thread'\n",
|
||||
"# Snapshot file\nexport CODEX_THREAD_ID='parent-thread'\nexport CODEX_VERSION='old-version'\n",
|
||||
)
|
||||
.expect("write snapshot");
|
||||
let (session_shell, shell_snapshot) =
|
||||
@@ -507,24 +507,34 @@ fn maybe_wrap_shell_lc_with_snapshot_restores_codex_thread_id_from_env() {
|
||||
let command = vec![
|
||||
"/bin/bash".to_string(),
|
||||
"-lc".to_string(),
|
||||
"printf '%s' \"$CODEX_THREAD_ID\"".to_string(),
|
||||
"printf '%s|%s' \"$CODEX_THREAD_ID\" \"$CODEX_VERSION\"".to_string(),
|
||||
];
|
||||
let env = HashMap::from([
|
||||
("CODEX_THREAD_ID".to_string(), "nested-thread".to_string()),
|
||||
(
|
||||
"CODEX_VERSION".to_string(),
|
||||
env!("CARGO_PKG_VERSION").to_string(),
|
||||
),
|
||||
]);
|
||||
let rewritten = maybe_wrap_shell_lc_with_snapshot(
|
||||
&command,
|
||||
&session_shell,
|
||||
Some(&shell_snapshot),
|
||||
&HashMap::new(),
|
||||
&HashMap::from([("CODEX_THREAD_ID".to_string(), "nested-thread".to_string())]),
|
||||
&env,
|
||||
&RuntimePathPrepends::default(),
|
||||
);
|
||||
let output = Command::new(&rewritten[0])
|
||||
.args(&rewritten[1..])
|
||||
.env("CODEX_THREAD_ID", "nested-thread")
|
||||
.envs(env)
|
||||
.output()
|
||||
.expect("run rewritten command");
|
||||
|
||||
assert!(output.status.success(), "command failed: {output:?}");
|
||||
assert_eq!(String::from_utf8_lossy(&output.stdout), "nested-thread");
|
||||
assert_eq!(
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
concat!("nested-thread|", env!("CARGO_PKG_VERSION")),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -45,6 +45,7 @@ use codex_utils_git_discovery::GitRootDiscovery;
|
||||
const MEMORY_GIT_METADATA_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(1);
|
||||
|
||||
const MODEL_KEY: &str = "model";
|
||||
const CODEX_VERSION_KEY: &str = "codex_version";
|
||||
const REASONING_EFFORT_KEY: &str = "reasoning_effort";
|
||||
const USER_INPUT_REQUESTED_DURING_TURN_KEY: &str = "user_input_requested_during_turn";
|
||||
const WORKSPACE_KIND_KEY: &str = "workspace_kind";
|
||||
@@ -246,6 +247,10 @@ impl TurnMetadataState {
|
||||
MODEL_KEY.to_string(),
|
||||
Value::String(context.model.to_string()),
|
||||
);
|
||||
metadata.insert(
|
||||
CODEX_VERSION_KEY.to_string(),
|
||||
Value::String(env!("CARGO_PKG_VERSION").to_string()),
|
||||
);
|
||||
match context.reasoning_effort {
|
||||
Some(reasoning_effort) => {
|
||||
metadata.insert(
|
||||
|
||||
@@ -739,6 +739,7 @@ fn turn_metadata_state_merges_client_metadata_without_replacing_reserved_fields(
|
||||
("workspace_kind".to_string(), "projectless".to_string()),
|
||||
("source".to_string(), "client-source".to_string()),
|
||||
("model".to_string(), "client-supplied".to_string()),
|
||||
("codex_version".to_string(), "client-supplied".to_string()),
|
||||
(
|
||||
"reasoning_effort".to_string(),
|
||||
"client-supplied".to_string(),
|
||||
@@ -932,6 +933,7 @@ fn turn_metadata_state_merges_client_metadata_without_replacing_reserved_fields(
|
||||
.current_meta_value_for_mcp_request(test_mcp_turn_metadata_context())
|
||||
.expect("turn metadata should be present");
|
||||
assert_eq!(meta["model"].as_str(), Some("gpt-5.4"));
|
||||
assert_eq!(meta["codex_version"], env!("CARGO_PKG_VERSION"));
|
||||
assert_eq!(meta["reasoning_effort"].as_str(), Some("high"));
|
||||
assert!(meta.get(LEGACY_CODE_MODE_TOOL_NAMES_KEY).is_none());
|
||||
assert!(meta.get(TOOL_NAMESPACES_INFO_KEY).is_none());
|
||||
|
||||
@@ -17,10 +17,11 @@ use super::oneshot::Completion;
|
||||
use crate::codex_thread::BackgroundTerminalInfo;
|
||||
use crate::exec_env::CODEX_PERMISSION_PROFILE_ENV_VAR;
|
||||
use crate::exec_env::CODEX_THREAD_ID_ENV_VAR;
|
||||
use crate::exec_env::CODEX_VERSION_ENV_VAR;
|
||||
use crate::exec_env::create_env;
|
||||
use crate::exec_env::inject_apply_patch_env;
|
||||
use crate::exec_env::inject_permission_profile_env;
|
||||
use crate::exec_env::inject_session_id_env;
|
||||
use crate::exec_env::inject_session_env;
|
||||
use crate::exec_policy::ExecApprovalRequest;
|
||||
use crate::guardian::GuardianReviewContext;
|
||||
use crate::plugins::metrics::finish_and_track_measurements;
|
||||
@@ -138,6 +139,7 @@ pub(super) fn exec_env_policy_from_shell_policy(
|
||||
.collect::<Vec<_>>();
|
||||
exclude.extend([
|
||||
CODEX_PERMISSION_PROFILE_ENV_VAR.to_string(),
|
||||
CODEX_VERSION_ENV_VAR.to_string(),
|
||||
codex_apply_patch::CODEX_APPLY_PATCH_PRESERVE_LINE_ENDINGS_ENV_VAR.to_string(),
|
||||
PLUGIN_METRICS_OUTPUT_ENV_VAR.to_string(),
|
||||
]);
|
||||
@@ -145,6 +147,7 @@ pub(super) fn exec_env_policy_from_shell_policy(
|
||||
r#set.retain(|key, _| {
|
||||
![
|
||||
CODEX_PERMISSION_PROFILE_ENV_VAR,
|
||||
CODEX_VERSION_ENV_VAR,
|
||||
codex_apply_patch::CODEX_APPLY_PATCH_PRESERVE_LINE_ENDINGS_ENV_VAR,
|
||||
PLUGIN_METRICS_OUTPUT_ENV_VAR,
|
||||
]
|
||||
@@ -176,6 +179,7 @@ fn env_overlay_for_exec_server(
|
||||
&& (matches!(
|
||||
key.as_str(),
|
||||
CODEX_PERMISSION_PROFILE_ENV_VAR
|
||||
| CODEX_VERSION_ENV_VAR
|
||||
| codex_apply_patch::CODEX_APPLY_PATCH_PRESERVE_LINE_ENDINGS_ENV_VAR
|
||||
) || local_policy_env.get(*key) != Some(*value))
|
||||
})
|
||||
@@ -1362,7 +1366,7 @@ impl UnifiedExecProcessManager {
|
||||
CODEX_THREAD_ID_ENV_VAR.to_string(),
|
||||
context.session.thread_id.to_string(),
|
||||
);
|
||||
inject_session_id_env(&mut env, context.session.session_id());
|
||||
inject_session_env(&mut env, context.session.session_id());
|
||||
inject_apply_patch_env(&mut env, &turn.config.features);
|
||||
let active_permission_profile = request.turn_environment.active_permission_profile();
|
||||
inject_permission_profile_env(&mut env, active_permission_profile.as_ref());
|
||||
|
||||
@@ -53,6 +53,10 @@ fn env_overlay_for_exec_server_keeps_runtime_changes_only() {
|
||||
("HOME".to_string(), "/client-home".to_string()),
|
||||
("PATH".to_string(), "/client-path".to_string()),
|
||||
("SHELL_SET".to_string(), "policy".to_string()),
|
||||
(
|
||||
CODEX_VERSION_ENV_VAR.to_string(),
|
||||
"client-version".to_string(),
|
||||
),
|
||||
(
|
||||
CODEX_PERMISSION_PROFILE_ENV_VAR.to_string(),
|
||||
"current-profile".to_string(),
|
||||
@@ -68,6 +72,10 @@ fn env_overlay_for_exec_server_keeps_runtime_changes_only() {
|
||||
("OpenAI_Federation_Rule_Id".to_string(), "rule".to_string()),
|
||||
("SHELL_SET".to_string(), "policy".to_string()),
|
||||
("CODEX_THREAD_ID".to_string(), "thread-1".to_string()),
|
||||
(
|
||||
CODEX_VERSION_ENV_VAR.to_string(),
|
||||
"client-version".to_string(),
|
||||
),
|
||||
(
|
||||
CODEX_PERMISSION_PROFILE_ENV_VAR.to_string(),
|
||||
"current-profile".to_string(),
|
||||
@@ -87,6 +95,10 @@ fn env_overlay_for_exec_server_keeps_runtime_changes_only() {
|
||||
HashMap::from([
|
||||
("PATH".to_string(), "/sandbox-path".to_string()),
|
||||
("CODEX_THREAD_ID".to_string(), "thread-1".to_string()),
|
||||
(
|
||||
CODEX_VERSION_ENV_VAR.to_string(),
|
||||
"client-version".to_string()
|
||||
),
|
||||
(
|
||||
CODEX_PERMISSION_PROFILE_ENV_VAR.to_string(),
|
||||
"current-profile".to_string(),
|
||||
@@ -107,6 +119,7 @@ fn env_overlay_for_exec_server_keeps_runtime_changes_only() {
|
||||
fn exec_env_policy_excludes_non_inheritable_and_runtime_variables() {
|
||||
let policy = ShellEnvironmentPolicy {
|
||||
r#set: HashMap::from([
|
||||
("codex_version".to_string(), "stale-version".to_string()),
|
||||
(
|
||||
"codex_permission_profile".to_string(),
|
||||
"stale-profile".to_string(),
|
||||
@@ -135,6 +148,7 @@ fn exec_env_policy_excludes_non_inheritable_and_runtime_variables() {
|
||||
ignore_default_excludes: policy.ignore_default_excludes,
|
||||
exclude: vec![
|
||||
CODEX_PERMISSION_PROFILE_ENV_VAR.to_string(),
|
||||
CODEX_VERSION_ENV_VAR.to_string(),
|
||||
codex_apply_patch::CODEX_APPLY_PATCH_PRESERVE_LINE_ENDINGS_ENV_VAR.to_string(),
|
||||
PLUGIN_METRICS_OUTPUT_ENV_VAR.to_string(),
|
||||
],
|
||||
|
||||
@@ -23,7 +23,7 @@ use crate::config::NetworkProxySpec;
|
||||
use crate::exec_env::CODEX_THREAD_ID_ENV_VAR;
|
||||
use crate::exec_env::inject_apply_patch_env;
|
||||
use crate::exec_env::inject_permission_profile_env;
|
||||
use crate::exec_env::inject_session_id_env;
|
||||
use crate::exec_env::inject_session_env;
|
||||
use crate::session::session::Session;
|
||||
use crate::session::step_context::StepContext;
|
||||
use crate::shell::ShellType;
|
||||
@@ -114,7 +114,7 @@ impl Session {
|
||||
CODEX_THREAD_ID_ENV_VAR.to_string(),
|
||||
session.thread_id().to_string(),
|
||||
);
|
||||
inject_session_id_env(&mut env, session.session_id());
|
||||
inject_session_env(&mut env, session.session_id());
|
||||
inject_apply_patch_env(&mut env, &config.features);
|
||||
inject_permission_profile_env(
|
||||
&mut env,
|
||||
|
||||
@@ -527,6 +527,10 @@ async fn approved_mcp_tool_call_metadata_records_prior_user_input_request(
|
||||
let mcp_turn_metadata = apps_tool_call
|
||||
.pointer("/params/_meta/x-codex-turn-metadata")
|
||||
.expect("MCP tools/call turn metadata");
|
||||
assert_eq!(
|
||||
mcp_turn_metadata["codex_version"],
|
||||
env!("CARGO_PKG_VERSION")
|
||||
);
|
||||
assert_eq!(
|
||||
(
|
||||
mcp_turn_metadata.get("root_turn_id"),
|
||||
|
||||
@@ -376,6 +376,10 @@ async fn exec_command_uses_installed_environment_shell_policy_with_explicit_over
|
||||
r#set: HashMap::from([
|
||||
("KEEP".to_string(), "preserved".to_string()),
|
||||
("DROP".to_string(), "filtered".to_string()),
|
||||
(
|
||||
"CODEX_VERSION".to_string(),
|
||||
"configured-version".to_string(),
|
||||
),
|
||||
]),
|
||||
..Default::default()
|
||||
};
|
||||
@@ -428,10 +432,10 @@ async fn exec_command_uses_installed_environment_shell_policy_with_explicit_over
|
||||
let call_id = "exec-command-environment-shell-policy";
|
||||
let command = match core_test_support::test_target_os() {
|
||||
core_test_support::TestTargetOs::Linux | core_test_support::TestTargetOs::MacOs => {
|
||||
r#"printf '%s:%s:%s' "$KEEP" "${DROP:-missing}" "${OWNER_ONLY:-missing}""#
|
||||
r#"printf '%s:%s:%s:%s' "$KEEP" "${DROP:-missing}" "${OWNER_ONLY:-missing}" "$CODEX_VERSION""#
|
||||
}
|
||||
core_test_support::TestTargetOs::Windows => {
|
||||
r#"if (Test-Path Env:DROP) { $drop = $env:DROP } else { $drop = 'missing' }; if (Test-Path Env:OWNER_ONLY) { $owner = $env:OWNER_ONLY } else { $owner = 'missing' }; Write-Output "${env:KEEP}:${drop}:${owner}""#
|
||||
r#"if (Test-Path Env:DROP) { $drop = $env:DROP } else { $drop = 'missing' }; if (Test-Path Env:OWNER_ONLY) { $owner = $env:OWNER_ONLY } else { $owner = 'missing' }; Write-Output "${env:KEEP}:${drop}:${owner}:${env:CODEX_VERSION}""#
|
||||
}
|
||||
};
|
||||
let arguments = json!({
|
||||
@@ -462,7 +466,10 @@ async fn exec_command_uses_installed_environment_shell_policy_with_explicit_over
|
||||
)
|
||||
.await?;
|
||||
let output = parse_unified_exec_output(&harness.function_call_stdout(call_id).await)?;
|
||||
assert_eq!(output.output.trim(), "preserved:missing:missing");
|
||||
assert_eq!(
|
||||
output.output.trim(),
|
||||
concat!("preserved:missing:missing:", env!("CARGO_PKG_VERSION")),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user