Require runtime paths for environments

Make EnvironmentManagerArgs carry ExecServerRuntimePaths for production construction and route test-only unsandboxed setup through explicit _for_tests helpers. Use the manager local environment for MCP and app-server filesystem fallbacks instead of constructing a fresh default environment.

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
starr-openai
2026-04-21 12:44:35 -07:00
co-authored by Codex
parent 6967e3f100
commit fc7a440ec9
32 changed files with 217 additions and 215 deletions
+6 -4
View File
@@ -970,9 +970,7 @@ mod tests {
cloud_requirements: CloudRequirementsLoader::default(),
feedback: CodexFeedback::new(),
log_db: None,
environment_manager: Arc::new(EnvironmentManager::new(
codex_exec_server::EnvironmentManagerArgs::default_for_tests(),
)),
environment_manager: Arc::new(EnvironmentManager::default_for_tests()),
config_warnings: Vec::new(),
session_source,
enable_codex_api_key_env: false,
@@ -1975,7 +1973,11 @@ mod tests {
let config = Arc::new(build_test_config().await);
let environment_manager = Arc::new(EnvironmentManager::new(EnvironmentManagerArgs {
exec_server_url: Some("ws://127.0.0.1:8765".to_string()),
local_runtime_paths: None,
local_runtime_paths: ExecServerRuntimePaths::new(
std::env::current_exe().expect("current exe"),
/*codex_linux_sandbox_exe*/ None,
)
.expect("runtime paths"),
}));
let runtime_args = InProcessClientStartArgs {
@@ -3497,9 +3497,7 @@ mod tests {
CodexAuth::create_dummy_chatgpt_auth_for_testing(),
config.model_provider.clone(),
config.codex_home.to_path_buf(),
Arc::new(codex_exec_server::EnvironmentManager::new(
codex_exec_server::EnvironmentManagerArgs::default_for_tests(),
)),
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
),
);
let codex_core::NewThread {
@@ -5694,18 +5694,15 @@ impl CodexMessageProcessor {
.await;
let auth_manager = Arc::clone(&self.auth_manager);
let auth = auth_manager.auth().await;
let runtime_environment = match self
.thread_manager
.environment_manager()
.default_environment()
{
let environment_manager = self.thread_manager.environment_manager();
let runtime_environment = match environment_manager.default_environment() {
Some(environment) => {
// Status listing has no turn cwd. This fallback is used only
// by executor-backed stdio MCPs whose config omits `cwd`.
McpRuntimeEnvironment::new(environment, config.cwd.to_path_buf())
}
None => McpRuntimeEnvironment::new(
Arc::new(codex_exec_server::Environment::default()),
environment_manager.local_environment(),
config.cwd.to_path_buf(),
),
};
@@ -5858,11 +5855,10 @@ impl CodexMessageProcessor {
.await;
let auth = self.auth_manager.auth().await;
let runtime_environment = {
let environment = self
.thread_manager
.environment_manager()
let environment_manager = self.thread_manager.environment_manager();
let environment = environment_manager
.default_environment()
.unwrap_or_else(|| Arc::new(codex_exec_server::Environment::default()));
.unwrap_or_else(|| environment_manager.local_environment());
// Resource reads without a thread have no turn cwd. This fallback
// is used only by executor-backed stdio MCPs whose config omits `cwd`.
McpRuntimeEnvironment::new(environment, config.cwd.to_path_buf())
+4 -9
View File
@@ -20,7 +20,6 @@ use codex_app_server_protocol::FsWriteFileResponse;
use codex_app_server_protocol::JSONRPCErrorError;
use codex_exec_server::CopyOptions;
use codex_exec_server::CreateDirectoryOptions;
use codex_exec_server::Environment;
use codex_exec_server::ExecutorFileSystem;
use codex_exec_server::RemoveOptions;
use std::io;
@@ -31,15 +30,11 @@ pub(crate) struct FsApi {
file_system: Arc<dyn ExecutorFileSystem>,
}
impl Default for FsApi {
fn default() -> Self {
Self {
file_system: Environment::default().get_filesystem(),
}
}
}
impl FsApi {
pub(crate) fn new(file_system: Arc<dyn ExecutorFileSystem>) -> Self {
Self { file_system }
}
pub(crate) async fn read_file(
&self,
params: FsReadFileParams,
+1 -3
View File
@@ -738,9 +738,7 @@ mod tests {
thread_config_loader: Arc::new(codex_config::NoopThreadConfigLoader),
feedback: CodexFeedback::new(),
log_db: None,
environment_manager: Arc::new(EnvironmentManager::new(
codex_exec_server::EnvironmentManagerArgs::default_for_tests(),
)),
environment_manager: Arc::new(EnvironmentManager::default_for_tests()),
config_warnings: Vec::new(),
session_source,
enable_codex_api_key_env: false,
+4 -5
View File
@@ -362,13 +362,12 @@ pub async fn run_main_with_transport(
session_source: SessionSource,
auth: AppServerWebsocketAuthSettings,
) -> IoResult<()> {
let environment_manager = Arc::new(EnvironmentManager::new(EnvironmentManagerArgs {
local_runtime_paths: Some(ExecServerRuntimePaths::from_optional_paths(
let environment_manager = Arc::new(EnvironmentManager::new(EnvironmentManagerArgs::from_env(
ExecServerRuntimePaths::from_optional_paths(
arg0_paths.codex_self_exe.clone(),
arg0_paths.codex_linux_sandbox_exe.clone(),
)?),
..EnvironmentManagerArgs::default()
}));
)?,
)));
let (transport_event_tx, mut transport_event_rx) =
mpsc::channel::<TransportEvent>(CHANNEL_CAPACITY);
let (outgoing_tx, mut outgoing_rx) = mpsc::channel::<OutgoingEnvelope>(CHANNEL_CAPACITY);
+6 -1
View File
@@ -327,7 +327,12 @@ impl MessageProcessor {
);
let external_agent_config_api =
ExternalAgentConfigApi::new(config.codex_home.to_path_buf());
let fs_api = FsApi::default();
let fs_api = FsApi::new(
thread_manager
.environment_manager()
.local_environment()
.get_filesystem(),
);
let fs_watch_manager = FsWatchManager::new(outgoing.clone());
Self {
@@ -241,9 +241,7 @@ fn build_test_processor(
outgoing,
arg0_paths: Arg0DispatchPaths::default(),
config,
environment_manager: Arc::new(EnvironmentManager::new(
codex_exec_server::EnvironmentManagerArgs::default_for_tests(),
)),
environment_manager: Arc::new(EnvironmentManager::default_for_tests()),
cli_overrides: Vec::new(),
loader_overrides: LoaderOverrides::default(),
cloud_requirements: CloudRequirementsLoader::default(),
@@ -204,9 +204,7 @@ async fn mcp_resource_read_returns_error_for_unknown_thread() -> Result<()> {
thread_config_loader: Arc::new(codex_config::NoopThreadConfigLoader),
feedback: CodexFeedback::new(),
log_db: None,
environment_manager: Arc::new(EnvironmentManager::new(
codex_exec_server::EnvironmentManagerArgs::default_for_tests(),
)),
environment_manager: Arc::new(EnvironmentManager::default_for_tests()),
config_warnings: Vec::new(),
session_source: SessionSource::Cli,
enable_codex_api_key_env: false,
+7 -21
View File
@@ -95,9 +95,7 @@ impl AgentControlHarness {
CodexAuth::from_api_key("dummy"),
config.model_provider.clone(),
config.codex_home.to_path_buf(),
std::sync::Arc::new(codex_exec_server::EnvironmentManager::new(
codex_exec_server::EnvironmentManagerArgs::default_for_tests(),
)),
std::sync::Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
);
let control = manager.agent_control();
Self {
@@ -911,9 +909,7 @@ async fn spawn_agent_respects_max_threads_limit() {
CodexAuth::from_api_key("dummy"),
config.model_provider.clone(),
config.codex_home.to_path_buf(),
std::sync::Arc::new(codex_exec_server::EnvironmentManager::new(
codex_exec_server::EnvironmentManagerArgs::default_for_tests(),
)),
std::sync::Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
);
let control = manager.agent_control();
@@ -965,9 +961,7 @@ async fn spawn_agent_releases_slot_after_shutdown() {
CodexAuth::from_api_key("dummy"),
config.model_provider.clone(),
config.codex_home.to_path_buf(),
std::sync::Arc::new(codex_exec_server::EnvironmentManager::new(
codex_exec_server::EnvironmentManagerArgs::default_for_tests(),
)),
std::sync::Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
);
let control = manager.agent_control();
@@ -1010,9 +1004,7 @@ async fn spawn_agent_limit_shared_across_clones() {
CodexAuth::from_api_key("dummy"),
config.model_provider.clone(),
config.codex_home.to_path_buf(),
std::sync::Arc::new(codex_exec_server::EnvironmentManager::new(
codex_exec_server::EnvironmentManagerArgs::default_for_tests(),
)),
std::sync::Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
);
let control = manager.agent_control();
let cloned = control.clone();
@@ -1057,9 +1049,7 @@ async fn resume_agent_respects_max_threads_limit() {
CodexAuth::from_api_key("dummy"),
config.model_provider.clone(),
config.codex_home.to_path_buf(),
std::sync::Arc::new(codex_exec_server::EnvironmentManager::new(
codex_exec_server::EnvironmentManagerArgs::default_for_tests(),
)),
std::sync::Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
);
let control = manager.agent_control();
@@ -1115,9 +1105,7 @@ async fn resume_agent_releases_slot_after_resume_failure() {
CodexAuth::from_api_key("dummy"),
config.model_provider.clone(),
config.codex_home.to_path_buf(),
std::sync::Arc::new(codex_exec_server::EnvironmentManager::new(
codex_exec_server::EnvironmentManagerArgs::default_for_tests(),
)),
std::sync::Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
);
let control = manager.agent_control();
@@ -1512,9 +1500,7 @@ async fn resume_thread_subagent_restores_stored_nickname_and_role() {
CodexAuth::from_api_key("dummy"),
config.model_provider.clone(),
config.codex_home.to_path_buf(),
std::sync::Arc::new(codex_exec_server::EnvironmentManager::new(
codex_exec_server::EnvironmentManagerArgs::default_for_tests(),
)),
std::sync::Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
);
let control = manager.agent_control();
let harness = AgentControlHarness {
+14 -2
View File
@@ -13,7 +13,9 @@ pub use codex_app_server_protocol::AppInfo;
pub use codex_app_server_protocol::AppMetadata;
use codex_connectors::AllConnectorsCacheKey;
use codex_connectors::DirectoryListResponse;
use codex_exec_server::Environment;
use codex_exec_server::EnvironmentManager;
use codex_exec_server::EnvironmentManagerArgs;
use codex_exec_server::ExecServerRuntimePaths;
use codex_login::token_data::TokenData;
use codex_protocol::protocol::SandboxPolicy;
use codex_tools::DiscoverableTool;
@@ -247,6 +249,16 @@ pub async fn list_accessible_connectors_from_mcp_tools_with_options_and_status(
let (tx_event, rx_event) = unbounded();
drop(rx_event);
let local_runtime_paths = ExecServerRuntimePaths::from_optional_paths(
config.codex_self_exe.clone(),
config.codex_linux_sandbox_exe.clone(),
)?;
let environment_manager =
EnvironmentManager::new(EnvironmentManagerArgs::from_env(local_runtime_paths));
let environment = environment_manager
.default_environment()
.unwrap_or_else(|| environment_manager.local_environment());
let (mcp_connection_manager, cancel_token) = McpConnectionManager::new(
&mcp_servers,
config.mcp_oauth_credentials_store_mode,
@@ -255,7 +267,7 @@ pub async fn list_accessible_connectors_from_mcp_tools_with_options_and_status(
INITIAL_SUBMIT_ID.to_owned(),
tx_event,
SandboxPolicy::new_read_only_policy(),
McpRuntimeEnvironment::new(Arc::new(Environment::default()), config.cwd.to_path_buf()),
McpRuntimeEnvironment::new(environment, config.cwd.to_path_buf()),
config.codex_home.to_path_buf(),
codex_apps_tools_cache_key(auth.as_ref()),
ToolPluginProvenance::default(),
+1 -3
View File
@@ -491,9 +491,7 @@ mod phase2 {
CodexAuth::from_api_key("dummy"),
config.model_provider.clone(),
config.codex_home.to_path_buf(),
std::sync::Arc::new(codex_exec_server::EnvironmentManager::new(
codex_exec_server::EnvironmentManagerArgs::default_for_tests(),
)),
std::sync::Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
);
let (mut session, _turn_context) = make_session_and_context().await;
session.services.state_db = Some(Arc::clone(&state_db));
+9 -1
View File
@@ -3,6 +3,7 @@ use std::sync::Arc;
use codex_exec_server::EnvironmentManager;
use codex_exec_server::EnvironmentManagerArgs;
use codex_exec_server::ExecServerRuntimePaths;
use codex_features::Feature;
use codex_login::AuthManager;
use codex_models_manager::collaboration_mode_presets::CollaborationModesConfig;
@@ -30,6 +31,11 @@ pub async fn build_prompt_input(
let auth_manager =
AuthManager::shared_from_config(&config, /*enable_codex_api_key_env*/ false);
let local_runtime_paths = ExecServerRuntimePaths::from_optional_paths(
config.codex_self_exe.clone(),
config.codex_linux_sandbox_exe.clone(),
)?;
let thread_manager = ThreadManager::new(
&config,
Arc::clone(&auth_manager),
@@ -39,7 +45,9 @@ pub async fn build_prompt_input(
.features
.enabled(Feature::DefaultModeRequestUserInput),
},
Arc::new(EnvironmentManager::new(EnvironmentManagerArgs::default())),
Arc::new(EnvironmentManager::new(EnvironmentManagerArgs::from_env(
local_runtime_paths,
))),
/*analytics_events_client*/ None,
);
let thread = thread_manager.start_thread(config).await?;
+1 -1
View File
@@ -250,7 +250,7 @@ impl Session {
turn_context
.environment
.clone()
.unwrap_or_else(|| Arc::new(Environment::default())),
.unwrap_or_else(|| self.services.environment_manager.local_environment()),
turn_context.cwd.to_path_buf(),
),
config.codex_home.to_path_buf(),
+1 -1
View File
@@ -793,7 +793,7 @@ impl Session {
sess.services
.environment_manager
.default_environment()
.unwrap_or_else(|| Arc::new(Environment::default())),
.unwrap_or_else(|| sess.services.environment_manager.local_environment()),
session_configuration.cwd.to_path_buf(),
),
config.codex_home.to_path_buf(),
+2 -2
View File
@@ -3179,7 +3179,7 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
));
let network_approval = Arc::new(NetworkApprovalService::default());
let environment = Arc::new(
codex_exec_server::Environment::create(/*exec_server_url*/ None)
codex_exec_server::Environment::create_for_tests(/*exec_server_url*/ None)
.expect("create environment"),
);
@@ -4277,7 +4277,7 @@ where
));
let network_approval = Arc::new(NetworkApprovalService::default());
let environment = Arc::new(
codex_exec_server::Environment::create(/*exec_server_url*/ None)
codex_exec_server::Environment::create_for_tests(/*exec_server_url*/ None)
.expect("create environment"),
);
@@ -634,9 +634,7 @@ async fn guardian_subagent_does_not_inherit_parent_exec_policy_rules() {
config,
auth_manager,
models_manager,
environment_manager: Arc::new(EnvironmentManager::new(
codex_exec_server::EnvironmentManagerArgs::default_for_tests(),
)),
environment_manager: Arc::new(EnvironmentManager::default_for_tests()),
skills_manager,
plugins_manager,
mcp_manager,
+1 -3
View File
@@ -301,9 +301,7 @@ impl ThreadManager {
auth,
provider,
codex_home.clone(),
Arc::new(EnvironmentManager::new(
codex_exec_server::EnvironmentManagerArgs::default_for_tests(),
)),
Arc::new(EnvironmentManager::default_for_tests()),
);
manager._test_codex_home_guard = Some(TempCodexHomeGuard { path: codex_home });
manager
+5 -15
View File
@@ -246,9 +246,7 @@ async fn shutdown_all_threads_bounded_submits_shutdown_to_every_thread() {
CodexAuth::from_api_key("dummy"),
config.model_provider.clone(),
config.codex_home.to_path_buf(),
Arc::new(codex_exec_server::EnvironmentManager::new(
codex_exec_server::EnvironmentManagerArgs::default_for_tests(),
)),
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
);
let thread_1 = manager
.start_thread(config.clone())
@@ -297,9 +295,7 @@ async fn new_uses_configured_openai_provider_for_model_refresh() {
auth_manager,
SessionSource::Exec,
CollaborationModesConfig::default(),
Arc::new(codex_exec_server::EnvironmentManager::new(
codex_exec_server::EnvironmentManagerArgs::default_for_tests(),
)),
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
/*analytics_events_client*/ None,
);
@@ -434,9 +430,7 @@ async fn interrupted_fork_snapshot_does_not_synthesize_turn_id_for_legacy_histor
auth_manager.clone(),
SessionSource::Exec,
CollaborationModesConfig::default(),
Arc::new(codex_exec_server::EnvironmentManager::new(
codex_exec_server::EnvironmentManagerArgs::default_for_tests(),
)),
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
/*analytics_events_client*/ None,
);
@@ -537,9 +531,7 @@ async fn interrupted_fork_snapshot_preserves_explicit_turn_id() {
auth_manager.clone(),
SessionSource::Exec,
CollaborationModesConfig::default(),
Arc::new(codex_exec_server::EnvironmentManager::new(
codex_exec_server::EnvironmentManagerArgs::default_for_tests(),
)),
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
/*analytics_events_client*/ None,
);
@@ -630,9 +622,7 @@ async fn interrupted_fork_snapshot_uses_persisted_mid_turn_history_without_live_
auth_manager.clone(),
SessionSource::Exec,
CollaborationModesConfig::default(),
Arc::new(codex_exec_server::EnvironmentManager::new(
codex_exec_server::EnvironmentManagerArgs::default_for_tests(),
)),
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
/*analytics_events_client*/ None,
);
+1 -1
View File
@@ -508,7 +508,7 @@ async fn completed_pipe_commands_preserve_exit_code() -> anyhow::Result<()> {
shell_env(),
);
let environment = codex_exec_server::Environment::default();
let environment = codex_exec_server::Environment::default_for_tests();
let process = UnifiedExecProcessManager::default()
.open_session_with_exec_env(
/*process_id*/ 1234,
+8 -3
View File
@@ -76,7 +76,8 @@ impl TestEnv {
pub async fn local() -> Result<Self> {
let local_cwd_temp_dir = Arc::new(TempDir::new()?);
let cwd = local_cwd_temp_dir.abs();
let environment = codex_exec_server::Environment::create(/*exec_server_url*/ None)?;
let environment =
codex_exec_server::Environment::create_for_tests(/*exec_server_url*/ None)?;
Ok(Self {
environment,
cwd,
@@ -115,7 +116,8 @@ pub async fn test_env() -> Result<TestEnv> {
match get_remote_test_env() {
Some(remote_env) => {
let websocket_url = remote_exec_server_url()?;
let environment = codex_exec_server::Environment::create(Some(websocket_url))?;
let environment =
codex_exec_server::Environment::create_for_tests(Some(websocket_url))?;
let cwd = remote_aware_cwd_path();
environment
.get_filesystem()
@@ -363,7 +365,10 @@ impl TestCodexBuilder {
let environment_manager = Arc::new(codex_exec_server::EnvironmentManager::new(
codex_exec_server::EnvironmentManagerArgs {
exec_server_url,
local_runtime_paths: None,
local_runtime_paths: codex_exec_server::ExecServerRuntimePaths::new(
std::env::current_exe()?,
/*codex_linux_sandbox_exe*/ None,
)?,
},
));
let file_system = test_env.environment().get_filesystem();
+1 -3
View File
@@ -1103,9 +1103,7 @@ async fn prefers_apikey_when_config_prefers_apikey_even_with_chatgpt_tokens() {
.features
.enabled(Feature::DefaultModeRequestUserInput),
},
Arc::new(codex_exec_server::EnvironmentManager::new(
codex_exec_server::EnvironmentManagerArgs::default_for_tests(),
)),
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
/*analytics_events_client*/ None,
);
let NewThread { thread: codex, .. } = thread_manager
+5 -1
View File
@@ -5,6 +5,7 @@ use anyhow::Result;
use codex_core::ThreadManager;
use codex_exec_server::CreateDirectoryOptions;
use codex_exec_server::EnvironmentManager;
use codex_exec_server::ExecServerRuntimePaths;
use codex_exec_server::ExecutorFileSystem;
use codex_login::CodexAuth;
use codex_models_manager::collaboration_mode_presets::CollaborationModesConfig;
@@ -237,7 +238,10 @@ async fn list_skills_skips_cwd_roots_when_environment_disabled() -> Result<()> {
Arc::new(EnvironmentManager::new(
codex_exec_server::EnvironmentManagerArgs {
exec_server_url: Some("none".to_string()),
local_runtime_paths: None,
local_runtime_paths: ExecServerRuntimePaths::new(
std::env::current_exe()?,
/*codex_linux_sandbox_exe*/ None,
)?,
},
)),
/*analytics_events_client*/ None,
+99 -68
View File
@@ -42,51 +42,40 @@ pub const REMOTE_ENVIRONMENT_ID: &str = "remote";
#[derive(Clone, Debug)]
pub struct EnvironmentManagerArgs {
pub exec_server_url: Option<String>,
pub local_runtime_paths: Option<ExecServerRuntimePaths>,
}
impl Default for EnvironmentManagerArgs {
fn default() -> Self {
Self {
exec_server_url: std::env::var(CODEX_EXEC_SERVER_URL_ENV_VAR).ok(),
local_runtime_paths: None,
}
}
pub local_runtime_paths: ExecServerRuntimePaths,
}
impl EnvironmentManagerArgs {
pub fn default_for_tests() -> Self {
pub fn new(local_runtime_paths: ExecServerRuntimePaths) -> Self {
Self {
exec_server_url: None,
local_runtime_paths: None,
local_runtime_paths,
}
}
}
impl From<Option<String>> for EnvironmentManagerArgs {
fn from(exec_server_url: Option<String>) -> Self {
pub fn from_env(local_runtime_paths: ExecServerRuntimePaths) -> Self {
Self {
exec_server_url,
local_runtime_paths: None,
exec_server_url: std::env::var(CODEX_EXEC_SERVER_URL_ENV_VAR).ok(),
local_runtime_paths,
}
}
}
impl Default for EnvironmentManager {
fn default() -> Self {
Self::new(EnvironmentManagerArgs::default())
}
}
impl EnvironmentManager {
/// Builds a test-only manager without configured sandbox helper paths.
pub fn default_for_tests() -> Self {
Self::new(EnvironmentManagerArgs::default_for_tests())
Self {
default_environment: Some(LOCAL_ENVIRONMENT_ID.to_string()),
environments: HashMap::from([(
LOCAL_ENVIRONMENT_ID.to_string(),
Arc::new(Environment::default_for_tests()),
)]),
}
}
/// Builds a manager from the raw `CODEX_EXEC_SERVER_URL` value and local
/// runtime paths used when creating local filesystem helpers.
pub fn new(exec_server_url: impl Into<EnvironmentManagerArgs>) -> Self {
let args = exec_server_url.into();
pub fn new(args: EnvironmentManagerArgs) -> Self {
let EnvironmentManagerArgs {
exec_server_url,
local_runtime_paths,
@@ -94,9 +83,7 @@ impl EnvironmentManager {
let (exec_server_url, environment_disabled) = normalize_exec_server_url(exec_server_url);
let mut environments = HashMap::from([(
LOCAL_ENVIRONMENT_ID.to_string(),
Arc::new(Environment::local_with_runtime_paths(
local_runtime_paths.clone(),
)),
Arc::new(Environment::local(local_runtime_paths.clone())),
)]);
let default_environment = if environment_disabled {
None
@@ -105,10 +92,7 @@ impl EnvironmentManager {
Some(exec_server_url) => {
environments.insert(
REMOTE_ENVIRONMENT_ID.to_string(),
Arc::new(Environment::remote_with_runtime_paths(
exec_server_url,
local_runtime_paths,
)),
Arc::new(Environment::remote(exec_server_url, local_runtime_paths)),
);
Some(REMOTE_ENVIRONMENT_ID.to_string())
}
@@ -129,6 +113,12 @@ impl EnvironmentManager {
.and_then(|environment_id| self.get_environment(environment_id))
}
/// Returns the local environment instance used for internal runtime work.
pub fn local_environment(&self) -> Arc<Environment> {
self.get_environment(LOCAL_ENVIRONMENT_ID)
.expect("EnvironmentManager always has a local environment")
}
/// Returns a named environment instance.
pub fn get_environment(&self, environment_id: &str) -> Option<Arc<Environment>> {
self.environments.get(environment_id).cloned()
@@ -147,8 +137,9 @@ pub struct Environment {
local_runtime_paths: Option<ExecServerRuntimePaths>,
}
impl Default for Environment {
fn default() -> Self {
impl Environment {
/// Builds a test-only local environment without configured sandbox helper paths.
pub fn default_for_tests() -> Self {
Self {
exec_server_url: None,
exec_backend: Arc::new(LocalProcess::default()),
@@ -168,13 +159,21 @@ impl std::fmt::Debug for Environment {
impl Environment {
/// Builds an environment from the raw `CODEX_EXEC_SERVER_URL` value.
pub fn create(exec_server_url: Option<String>) -> Result<Self, ExecServerError> {
Self::create_with_runtime_paths(exec_server_url, /*local_runtime_paths*/ None)
pub fn create(
exec_server_url: Option<String>,
local_runtime_paths: ExecServerRuntimePaths,
) -> Result<Self, ExecServerError> {
Self::create_inner(exec_server_url, Some(local_runtime_paths))
}
/// Builds a test-only environment without configured sandbox helper paths.
pub fn create_for_tests(exec_server_url: Option<String>) -> Result<Self, ExecServerError> {
Self::create_inner(exec_server_url, /*local_runtime_paths*/ None)
}
/// Builds an environment from the raw `CODEX_EXEC_SERVER_URL` value and
/// local runtime paths used when creating local filesystem helpers.
pub(crate) fn create_with_runtime_paths(
fn create_inner(
exec_server_url: Option<String>,
local_runtime_paths: Option<ExecServerRuntimePaths>,
) -> Result<Self, ExecServerError> {
@@ -186,28 +185,30 @@ impl Environment {
}
Ok(match exec_server_url {
Some(exec_server_url) => {
Self::remote_with_runtime_paths(exec_server_url, local_runtime_paths)
}
None => Self::local_with_runtime_paths(local_runtime_paths),
Some(exec_server_url) => Self::remote_inner(exec_server_url, local_runtime_paths),
None => match local_runtime_paths {
Some(local_runtime_paths) => Self::local(local_runtime_paths),
None => Self::default_for_tests(),
},
})
}
fn local_with_runtime_paths(local_runtime_paths: Option<ExecServerRuntimePaths>) -> Self {
let filesystem: Arc<dyn ExecutorFileSystem> = match local_runtime_paths.clone() {
Some(runtime_paths) => Arc::new(LocalFileSystem::with_runtime_paths(runtime_paths)),
None => Arc::new(LocalFileSystem::unsandboxed()),
};
fn local(local_runtime_paths: ExecServerRuntimePaths) -> Self {
Self {
exec_server_url: None,
exec_backend: Arc::new(LocalProcess::default()),
filesystem,
local_runtime_paths,
filesystem: Arc::new(LocalFileSystem::with_runtime_paths(
local_runtime_paths.clone(),
)),
local_runtime_paths: Some(local_runtime_paths),
}
}
fn remote_with_runtime_paths(
fn remote(exec_server_url: String, local_runtime_paths: ExecServerRuntimePaths) -> Self {
Self::remote_inner(exec_server_url, Some(local_runtime_paths))
}
fn remote_inner(
exec_server_url: String,
local_runtime_paths: Option<ExecServerRuntimePaths>,
) -> Self {
@@ -265,10 +266,18 @@ mod tests {
use crate::ProcessId;
use pretty_assertions::assert_eq;
fn test_runtime_paths() -> ExecServerRuntimePaths {
ExecServerRuntimePaths::new(
std::env::current_exe().expect("current exe"),
/*codex_linux_sandbox_exe*/ None,
)
.expect("runtime paths")
}
#[tokio::test]
async fn create_local_environment_does_not_connect() {
let environment =
Environment::create(/*exec_server_url*/ None).expect("create environment");
let environment = Environment::create(/*exec_server_url*/ None, test_runtime_paths())
.expect("create environment");
assert_eq!(environment.exec_server_url(), None);
assert!(!environment.is_remote());
@@ -278,7 +287,7 @@ mod tests {
async fn environment_manager_normalizes_empty_url() {
let manager = EnvironmentManager::new(EnvironmentManagerArgs {
exec_server_url: Some(String::new()),
local_runtime_paths: None,
local_runtime_paths: test_runtime_paths(),
});
let environment = manager.default_environment().expect("default environment");
@@ -296,7 +305,7 @@ mod tests {
async fn environment_manager_treats_none_value_as_disabled() {
let manager = EnvironmentManager::new(EnvironmentManagerArgs {
exec_server_url: Some("none".to_string()),
local_runtime_paths: None,
local_runtime_paths: test_runtime_paths(),
});
assert!(manager.default_environment().is_none());
@@ -313,7 +322,7 @@ mod tests {
async fn environment_manager_reports_remote_url() {
let manager = EnvironmentManager::new(EnvironmentManagerArgs {
exec_server_url: Some("ws://127.0.0.1:8765".to_string()),
local_runtime_paths: None,
local_runtime_paths: test_runtime_paths(),
});
let environment = manager.default_environment().expect("default environment");
@@ -335,7 +344,7 @@ mod tests {
#[tokio::test]
async fn environment_manager_default_environment_caches_environment() {
let manager = EnvironmentManager::new(EnvironmentManagerArgs::default_for_tests());
let manager = EnvironmentManager::default_for_tests();
let first = manager.default_environment().expect("default environment");
let second = manager.default_environment().expect("default environment");
@@ -349,14 +358,10 @@ mod tests {
#[tokio::test]
async fn environment_manager_carries_local_runtime_paths() {
let runtime_paths = ExecServerRuntimePaths::new(
std::env::current_exe().expect("current exe"),
/*codex_linux_sandbox_exe*/ None,
)
.expect("runtime paths");
let runtime_paths = test_runtime_paths();
let manager = EnvironmentManager::new(EnvironmentManagerArgs {
exec_server_url: None,
local_runtime_paths: Some(runtime_paths.clone()),
local_runtime_paths: runtime_paths.clone(),
});
let environment = manager.default_environment().expect("default environment");
@@ -364,7 +369,10 @@ mod tests {
assert_eq!(environment.local_runtime_paths(), Some(&runtime_paths));
let manager = EnvironmentManager::new(EnvironmentManagerArgs {
exec_server_url: environment.exec_server_url().map(str::to_owned),
local_runtime_paths: environment.local_runtime_paths().cloned(),
local_runtime_paths: environment
.local_runtime_paths()
.expect("local runtime paths")
.clone(),
});
let environment = manager.default_environment().expect("default environment");
assert_eq!(environment.local_runtime_paths(), Some(&runtime_paths));
@@ -374,7 +382,7 @@ mod tests {
async fn disabled_environment_manager_has_no_default_environment() {
let manager = EnvironmentManager::new(EnvironmentManagerArgs {
exec_server_url: Some("none".to_string()),
local_runtime_paths: None,
local_runtime_paths: test_runtime_paths(),
});
assert!(manager.default_environment().is_none());
@@ -384,7 +392,7 @@ mod tests {
async fn environment_manager_keeps_local_lookup_when_default_disabled() {
let manager = EnvironmentManager::new(EnvironmentManagerArgs {
exec_server_url: Some("none".to_string()),
local_runtime_paths: None,
local_runtime_paths: test_runtime_paths(),
});
assert!(manager.default_environment().is_none());
@@ -399,14 +407,14 @@ mod tests {
#[tokio::test]
async fn get_environment_returns_none_for_unknown_id() {
let manager = EnvironmentManager::new(EnvironmentManagerArgs::default_for_tests());
let manager = EnvironmentManager::default_for_tests();
assert!(manager.get_environment("does-not-exist").is_none());
}
#[tokio::test]
async fn default_environment_has_ready_local_executor() {
let environment = Environment::default();
let environment = Environment::default_for_tests();
let response = environment
.get_exec_backend()
@@ -425,4 +433,27 @@ mod tests {
assert_eq!(response.process.process_id().as_str(), "default-env-proc");
}
#[tokio::test]
async fn test_environment_rejects_sandboxed_filesystem_without_runtime_paths() {
let environment = Environment::default_for_tests();
let path = codex_utils_absolute_path::AbsolutePathBuf::from_absolute_path(
std::env::current_exe().expect("current exe").as_path(),
)
.expect("absolute current exe");
let sandbox = crate::FileSystemSandboxContext::new(
codex_protocol::protocol::SandboxPolicy::new_read_only_policy(),
);
let err = environment
.get_filesystem()
.read_file(&path, Some(&sandbox))
.await
.expect_err("sandboxed read should require runtime paths");
assert_eq!(
err.to_string(),
"sandboxed filesystem operations require configured runtime paths"
);
}
}
+2 -2
View File
@@ -49,13 +49,13 @@ enum ProcessEventSnapshot {
async fn create_process_context(use_remote: bool) -> Result<ProcessContext> {
if use_remote {
let server = exec_server().await?;
let environment = Environment::create(Some(server.websocket_url().to_string()))?;
let environment = Environment::create_for_tests(Some(server.websocket_url().to_string()))?;
Ok(ProcessContext {
backend: environment.get_exec_backend(),
server: Some(server),
})
} else {
let environment = Environment::create(/*exec_server_url*/ None)?;
let environment = Environment::create_for_tests(/*exec_server_url*/ None)?;
Ok(ProcessContext {
backend: environment.get_exec_backend(),
server: None,
+2 -2
View File
@@ -46,7 +46,7 @@ struct FileSystemContext {
async fn create_file_system_context(use_remote: bool) -> Result<FileSystemContext> {
if use_remote {
let server = exec_server().await?;
let environment = Environment::create(Some(server.websocket_url().to_string()))?;
let environment = Environment::create_for_tests(Some(server.websocket_url().to_string()))?;
Ok(FileSystemContext {
file_system: environment.get_filesystem(),
_helper_paths: None,
@@ -214,7 +214,7 @@ async fn sandboxed_file_system_helper_finds_bwrap_on_preserved_path() -> Result<
let helper_path = std::env::join_paths(path_entries)?;
let server = exec_server_with_env([("PATH", helper_path.as_os_str())]).await?;
let environment = Environment::create(Some(server.websocket_url().to_string()))?;
let environment = Environment::create_for_tests(Some(server.websocket_url().to_string()))?;
let file_system = environment.get_filesystem();
let workspace = tmp.path().join("workspace");
std::fs::create_dir_all(&workspace)?;
+3 -4
View File
@@ -498,10 +498,9 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result
cloud_requirements: run_cloud_requirements,
feedback: CodexFeedback::new(),
log_db: None,
environment_manager: std::sync::Arc::new(EnvironmentManager::new(EnvironmentManagerArgs {
local_runtime_paths: Some(local_runtime_paths),
..EnvironmentManagerArgs::default()
})),
environment_manager: std::sync::Arc::new(EnvironmentManager::new(
EnvironmentManagerArgs::from_env(local_runtime_paths),
)),
config_warnings,
session_source: SessionSource::Exec,
enable_codex_api_key_env: true,
+4 -5
View File
@@ -60,13 +60,12 @@ pub async fn run_main(
arg0_paths: Arg0DispatchPaths,
cli_config_overrides: CliConfigOverrides,
) -> IoResult<()> {
let environment_manager = Arc::new(EnvironmentManager::new(EnvironmentManagerArgs {
local_runtime_paths: Some(ExecServerRuntimePaths::from_optional_paths(
let environment_manager = Arc::new(EnvironmentManager::new(EnvironmentManagerArgs::from_env(
ExecServerRuntimePaths::from_optional_paths(
arg0_paths.codex_self_exe.clone(),
arg0_paths.codex_linux_sandbox_exe.clone(),
)?),
..EnvironmentManagerArgs::default()
}));
)?,
)));
// Parse CLI overrides once and derive the base Config eagerly so later
// components do not need to work with raw TOML values.
let cli_kv_overrides = cli_config_overrides.parse_overrides().map_err(|e| {
+1 -3
View File
@@ -38,9 +38,7 @@ pub(super) async fn make_test_app() -> App {
backtrack_render_pending: false,
feedback: codex_feedback::CodexFeedback::new(),
feedback_audience: FeedbackAudience::External,
environment_manager: Arc::new(EnvironmentManager::new(
codex_exec_server::EnvironmentManagerArgs::default_for_tests(),
)),
environment_manager: Arc::new(EnvironmentManager::default_for_tests()),
remote_app_server_url: None,
remote_app_server_auth_token: None,
pending_update_action: None,
+2 -6
View File
@@ -3576,9 +3576,7 @@ async fn make_test_app() -> App {
backtrack_render_pending: false,
feedback: codex_feedback::CodexFeedback::new(),
feedback_audience: FeedbackAudience::External,
environment_manager: Arc::new(EnvironmentManager::new(
codex_exec_server::EnvironmentManagerArgs::default_for_tests(),
)),
environment_manager: Arc::new(EnvironmentManager::default_for_tests()),
remote_app_server_url: None,
remote_app_server_auth_token: None,
pending_update_action: None,
@@ -3635,9 +3633,7 @@ async fn make_test_app_with_channels() -> (
backtrack_render_pending: false,
feedback: codex_feedback::CodexFeedback::new(),
feedback_audience: FeedbackAudience::External,
environment_manager: Arc::new(EnvironmentManager::new(
codex_exec_server::EnvironmentManagerArgs::default_for_tests(),
)),
environment_manager: Arc::new(EnvironmentManager::default_for_tests()),
remote_app_server_url: None,
remote_app_server_auth_token: None,
pending_update_action: None,
+14 -21
View File
@@ -426,9 +426,7 @@ pub(crate) async fn start_embedded_app_server_for_picker(
start_app_server_for_picker(
config,
&AppServerTarget::Embedded,
Arc::new(EnvironmentManager::new(
codex_exec_server::EnvironmentManagerArgs::default_for_tests(),
)),
Arc::new(EnvironmentManager::default_for_tests()),
)
.await
}
@@ -731,13 +729,12 @@ pub async fn run_main(
}
};
let environment_manager = Arc::new(EnvironmentManager::new(EnvironmentManagerArgs {
local_runtime_paths: Some(ExecServerRuntimePaths::from_optional_paths(
let environment_manager = Arc::new(EnvironmentManager::new(EnvironmentManagerArgs::from_env(
ExecServerRuntimePaths::from_optional_paths(
arg0_paths.codex_self_exe.clone(),
arg0_paths.codex_linux_sandbox_exe.clone(),
)?),
..EnvironmentManagerArgs::default()
}));
)?,
)));
let cwd = cli.cwd.clone();
let config_cwd =
config_cwd_for_app_server_target(cwd.as_deref(), &app_server_target, &environment_manager)?;
@@ -1777,9 +1774,7 @@ mod tests {
CloudRequirementsLoader::default(),
codex_feedback::CodexFeedback::new(),
/*log_db*/ None,
Arc::new(EnvironmentManager::new(
codex_exec_server::EnvironmentManagerArgs::default_for_tests(),
)),
Arc::new(EnvironmentManager::default_for_tests()),
)
.await
}
@@ -1939,8 +1934,7 @@ mod tests {
websocket_url: "ws://127.0.0.1:1234/".to_string(),
auth_token: None,
};
let environment_manager =
EnvironmentManager::new(codex_exec_server::EnvironmentManagerArgs::default_for_tests());
let environment_manager = EnvironmentManager::default_for_tests();
let config_cwd =
config_cwd_for_app_server_target(Some(remote_only_cwd), &target, &environment_manager)?;
@@ -1954,8 +1948,7 @@ mod tests {
{
let temp_dir = TempDir::new()?;
let target = AppServerTarget::Embedded;
let environment_manager =
EnvironmentManager::new(codex_exec_server::EnvironmentManagerArgs::default_for_tests());
let environment_manager = EnvironmentManager::default_for_tests();
let config_cwd =
config_cwd_for_app_server_target(Some(temp_dir.path()), &target, &environment_manager)?;
@@ -1975,8 +1968,7 @@ mod tests {
let temp_dir = TempDir::new()?;
let missing = temp_dir.path().join("missing");
let target = AppServerTarget::Embedded;
let environment_manager =
EnvironmentManager::new(codex_exec_server::EnvironmentManagerArgs::default_for_tests());
let environment_manager = EnvironmentManager::default_for_tests();
let err = config_cwd_for_app_server_target(Some(&missing), &target, &environment_manager)
.expect_err("missing embedded cwd should fail");
@@ -1997,7 +1989,10 @@ mod tests {
let environment_manager =
EnvironmentManager::new(codex_exec_server::EnvironmentManagerArgs {
exec_server_url: Some("ws://127.0.0.1:8765".to_string()),
local_runtime_paths: None,
local_runtime_paths: ExecServerRuntimePaths::new(
std::env::current_exe().expect("current exe"),
/*codex_linux_sandbox_exe*/ None,
)?,
});
let config_cwd =
@@ -2125,9 +2120,7 @@ mod tests {
CloudRequirementsLoader::default(),
codex_feedback::CodexFeedback::new(),
/*log_db*/ None,
Arc::new(EnvironmentManager::new(
codex_exec_server::EnvironmentManagerArgs::default_for_tests(),
)),
Arc::new(EnvironmentManager::default_for_tests()),
|_args| async { Err(std::io::Error::other("boom")) },
)
.await;
+3 -3
View File
@@ -989,9 +989,9 @@ mod tests {
),
feedback: codex_feedback::CodexFeedback::new(),
log_db: None,
environment_manager: Arc::new(codex_app_server_client::EnvironmentManager::new(
codex_app_server_client::EnvironmentManagerArgs::default_for_tests(),
)),
environment_manager: Arc::new(
codex_app_server_client::EnvironmentManager::default_for_tests(),
),
config_warnings: Vec::new(),
session_source: SessionSource::Cli,
enable_codex_api_key_env: false,