mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Build remote exec env from exec-server policy (#17216)
## Summary - add an exec-server `envPolicy` field; when present, the server starts from its own process env and applies the shell environment policy there - keep `env` as the exact environment for local/embedded starts, but make it an overlay for remote unified-exec starts - move the shell-environment-policy builder into `codex-config` so Core and exec-server share the inherit/filter/set/include behavior - overlay only runtime/sandbox/network deltas from Core onto the exec-server-derived env ## Why Remote unified exec was materializing the shell env inside Core and forwarding the whole map to exec-server, so remote processes could inherit the orchestrator machine's `HOME`, `PATH`, etc. This keeps the base env on the executor while preserving Core-owned runtime additions like `CODEX_THREAD_ID`, unified-exec defaults, network proxy env, and sandbox marker env. ## Validation - `just fmt` - `git diff --check` - `cargo test -p codex-exec-server --lib` - `cargo test -p codex-core --lib unified_exec::process_manager::tests` - `cargo test -p codex-core --lib exec_env::tests` - `cargo test -p codex-core --lib exec_env_tests` (compile-only; filter matched 0 tests) - `cargo test -p codex-config --lib shell_environment` (compile-only; filter matched 0 tests) - `just bazel-lock-update` ## Known local validation issue - `just bazel-lock-check` is not runnable in this checkout: it invokes `./scripts/check-module-bazel-lock.sh`, which is missing. --------- Co-authored-by: Codex <noreply@openai.com> Co-authored-by: pakrym-oai <pakrym@openai.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
4ffe6c2ce6
commit
bacb92b1d7
@@ -1,11 +1,10 @@
|
||||
#[cfg(test)]
|
||||
use codex_config::types::EnvironmentVariablePattern;
|
||||
use codex_config::types::ShellEnvironmentPolicy;
|
||||
use codex_config::types::ShellEnvironmentPolicyInherit;
|
||||
use codex_protocol::ThreadId;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
|
||||
pub const CODEX_THREAD_ID_ENV_VAR: &str = "CODEX_THREAD_ID";
|
||||
pub use codex_config::shell_environment::CODEX_THREAD_ID_ENV_VAR;
|
||||
|
||||
/// Construct an environment map based on the rules in the specified policy. The
|
||||
/// resulting map can be passed directly to `Command::envs()` after calling
|
||||
@@ -21,9 +20,11 @@ pub fn create_env(
|
||||
policy: &ShellEnvironmentPolicy,
|
||||
thread_id: Option<ThreadId>,
|
||||
) -> HashMap<String, String> {
|
||||
create_env_from_vars(std::env::vars(), policy, thread_id)
|
||||
let thread_id = thread_id.map(|thread_id| thread_id.to_string());
|
||||
codex_config::shell_environment::create_env(policy, thread_id.as_deref())
|
||||
}
|
||||
|
||||
#[cfg(all(test, target_os = "windows"))]
|
||||
fn create_env_from_vars<I>(
|
||||
vars: I,
|
||||
policy: &ShellEnvironmentPolicy,
|
||||
@@ -32,35 +33,11 @@ fn create_env_from_vars<I>(
|
||||
where
|
||||
I: IntoIterator<Item = (String, String)>,
|
||||
{
|
||||
let mut env_map = populate_env(vars, policy, thread_id);
|
||||
|
||||
if cfg!(target_os = "windows") {
|
||||
// This is a workaround to address the failures we are seeing in the
|
||||
// following tests when run via Bazel on Windows:
|
||||
//
|
||||
// ```
|
||||
// suite::shell_command::unicode_output::with_login
|
||||
// suite::shell_command::unicode_output::without_login
|
||||
// ```
|
||||
//
|
||||
// Currently, we can only reproduce these failures in CI, which makes
|
||||
// iteration times long, so we include this quick fix for now to unblock
|
||||
// getting the Windows Bazel build running.
|
||||
if !env_map.keys().any(|k| k.eq_ignore_ascii_case("PATHEXT")) {
|
||||
env_map.insert("PATHEXT".to_string(), ".COM;.EXE;.BAT;.CMD".to_string());
|
||||
}
|
||||
}
|
||||
env_map
|
||||
let thread_id = thread_id.map(|thread_id| thread_id.to_string());
|
||||
codex_config::shell_environment::create_env_from_vars(vars, policy, thread_id.as_deref())
|
||||
}
|
||||
|
||||
const COMMON_CORE_VARS: &[&str] = &["PATH", "SHELL", "TMPDIR", "TEMP", "TMP"];
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
const PLATFORM_CORE_VARS: &[&str] = &["PATHEXT", "USERNAME", "USERPROFILE"];
|
||||
|
||||
#[cfg(unix)]
|
||||
const PLATFORM_CORE_VARS: &[&str] = &["HOME", "LANG", "LC_ALL", "LC_CTYPE", "LOGNAME", "USER"];
|
||||
|
||||
#[cfg(test)]
|
||||
fn populate_env<I>(
|
||||
vars: I,
|
||||
policy: &ShellEnvironmentPolicy,
|
||||
@@ -69,66 +46,8 @@ fn populate_env<I>(
|
||||
where
|
||||
I: IntoIterator<Item = (String, String)>,
|
||||
{
|
||||
// Step 1 – determine the starting set of variables based on the
|
||||
// `inherit` strategy.
|
||||
let mut env_map: HashMap<String, String> = match policy.inherit {
|
||||
ShellEnvironmentPolicyInherit::All => vars.into_iter().collect(),
|
||||
ShellEnvironmentPolicyInherit::None => HashMap::new(),
|
||||
ShellEnvironmentPolicyInherit::Core => {
|
||||
let core_vars: HashSet<&str> = COMMON_CORE_VARS
|
||||
.iter()
|
||||
.copied()
|
||||
.chain(PLATFORM_CORE_VARS.iter().copied())
|
||||
.collect();
|
||||
let is_core_var = |name: &str| {
|
||||
if cfg!(target_os = "windows") {
|
||||
core_vars
|
||||
.iter()
|
||||
.any(|allowed| allowed.eq_ignore_ascii_case(name))
|
||||
} else {
|
||||
core_vars.contains(name)
|
||||
}
|
||||
};
|
||||
vars.into_iter().filter(|(k, _)| is_core_var(k)).collect()
|
||||
}
|
||||
};
|
||||
|
||||
// Internal helper – does `name` match **any** pattern in `patterns`?
|
||||
let matches_any = |name: &str, patterns: &[EnvironmentVariablePattern]| -> bool {
|
||||
patterns.iter().any(|pattern| pattern.matches(name))
|
||||
};
|
||||
|
||||
// Step 2 – Apply the default exclude if not disabled.
|
||||
if !policy.ignore_default_excludes {
|
||||
let default_excludes = vec![
|
||||
EnvironmentVariablePattern::new_case_insensitive("*KEY*"),
|
||||
EnvironmentVariablePattern::new_case_insensitive("*SECRET*"),
|
||||
EnvironmentVariablePattern::new_case_insensitive("*TOKEN*"),
|
||||
];
|
||||
env_map.retain(|k, _| !matches_any(k, &default_excludes));
|
||||
}
|
||||
|
||||
// Step 3 – Apply custom excludes.
|
||||
if !policy.exclude.is_empty() {
|
||||
env_map.retain(|k, _| !matches_any(k, &policy.exclude));
|
||||
}
|
||||
|
||||
// Step 4 – Apply user-provided overrides.
|
||||
for (key, val) in &policy.r#set {
|
||||
env_map.insert(key.clone(), val.clone());
|
||||
}
|
||||
|
||||
// Step 5 – If include_only is non-empty, keep *only* the matching vars.
|
||||
if !policy.include_only.is_empty() {
|
||||
env_map.retain(|k, _| matches_any(k, &policy.include_only));
|
||||
}
|
||||
|
||||
// Step 6 – Populate the thread ID environment variable when provided.
|
||||
if let Some(thread_id) = thread_id {
|
||||
env_map.insert(CODEX_THREAD_ID_ENV_VAR.to_string(), thread_id.to_string());
|
||||
}
|
||||
|
||||
env_map
|
||||
let thread_id = thread_id.map(|thread_id| thread_id.to_string());
|
||||
codex_config::shell_environment::populate_env(vars, policy, thread_id.as_deref())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
Reference in New Issue
Block a user