[codex] resolve environment shell metadata eagerly (#27709)

## Why

Turn construction passed resolved environments through several layers
while leaving the environment shell unresolved. As a result,
model-visible environment context could fall back to the session shell
instead of reporting the selected remote environment's shell.

Resolve environment metadata at the turn-context boundary so each turn
carries the shell that belongs to its selected environment. Keep request
validation in app-server, where invalid selections can be returned as
straightforward JSON-RPC errors without coupling core turn construction
to that policy.

## What changed

- resolve environment selections eagerly in
`new_turn_context_from_configuration`
- store the full resolved `Shell` on each `TurnEnvironment`
- simplify the now-redundant resolved-environment constructor plumbing
- keep duplicate and unknown-environment validation as a small
app-server preflight
- add a remote-environment integration test that runs a full
`test_codex` turn and verifies the model-visible environment message
reports `bash`

## Testing

- `cargo check -p codex-core --test all -p codex-app-server`
- `remote_test_env_exposes_bash_shell_to_model` on the Linux
remote-executor harness
This commit is contained in:
pakrym-oai authored and GitHub committed 2026-06-11 20:35:28 -07:00
1 parent e23d4df4ff
commit be338ee9a2
11 files changed
+162 -165

No files matched your search

+17 -48
View File
@@ -40,7 +40,7 @@ pub(crate) struct TurnEnvironment {
pub(crate) environment_id: String,
pub(crate) environment: Arc<Environment>,
pub(crate) cwd: AbsolutePathBuf,
pub(crate) shell: Option<String>,
pub(crate) shell: Option<shell::Shell>,
}
impl TurnEnvironment {
@@ -590,8 +590,6 @@ impl Session {
let mut state = self.state.lock().await;
match state.session_configuration.clone().apply(&updates) {
Ok(next) => {
let turn_environments =
self.resolve_turn_environments(next.environment_selections())?;
let previous_cwd = state.session_configuration.cwd().clone();
let previous_permission_profile =
state.session_configuration.permission_profile();
@@ -608,7 +606,6 @@ impl Session {
state.session_configuration = next.clone();
Ok((
next,
turn_environments,
permission_profile_changed,
previous_cwd,
codex_home,
@@ -623,7 +620,6 @@ impl Session {
let (
session_configuration,
turn_environments,
permission_profile_changed,
previous_cwd,
codex_home,
@@ -664,33 +660,20 @@ impl Session {
sub_id,
session_configuration,
updates.final_output_json_schema,
turn_environments,
)
.await)
}
fn resolve_turn_environments(
&self,
environments: &[TurnEnvironmentSelection],
) -> CodexResult<ResolvedTurnEnvironments> {
crate::environment_selection::resolve_environment_selections(
self.services.environment_manager.as_ref(),
environments,
)
}
async fn new_turn_from_configuration(
&self,
sub_id: String,
session_configuration: SessionConfiguration,
final_output_json_schema: Option<Option<Value>>,
turn_environments: ResolvedTurnEnvironments,
) -> Arc<TurnContext> {
self.new_turn_context_from_configuration(
sub_id,
session_configuration,
final_output_json_schema,
turn_environments,
TurnMultiAgentRuntime::ResolveAndStore,
)
.await
@@ -700,13 +683,11 @@ impl Session {
&self,
sub_id: String,
session_configuration: SessionConfiguration,
turn_environments: ResolvedTurnEnvironments,
) -> Arc<TurnContext> {
self.new_turn_context_from_configuration(
sub_id,
session_configuration,
/*final_output_json_schema*/ None,
turn_environments,
TurnMultiAgentRuntime::Preview,
)
.await
@@ -717,9 +698,17 @@ impl Session {
sub_id: String,
session_configuration: SessionConfiguration,
final_output_json_schema: Option<Option<Value>>,
turn_environments: ResolvedTurnEnvironments,
multi_agent_runtime: TurnMultiAgentRuntime,
) -> Arc<TurnContext> {
let turn_environments = crate::environment_selection::resolve_environment_selections(
self.services.environment_manager.as_ref(),
session_configuration.environment_selections(),
)
.await
.unwrap_or_else(|err| {
warn!("failed to resolve turn environments: {err}");
ResolvedTurnEnvironments::default()
});
let primary_turn_environment = turn_environments.primary().cloned();
let cwd = primary_turn_environment
.as_ref()
@@ -832,13 +821,11 @@ impl Session {
}
pub(crate) async fn new_default_turn_with_sub_id(&self, sub_id: String) -> Arc<TurnContext> {
let (session_configuration, turn_environments) =
self.default_turn_configuration_and_environments().await;
let session_configuration = self.default_turn_configuration().await;
self.new_turn_from_configuration(
sub_id,
session_configuration,
/*final_output_json_schema*/ None,
turn_environments,
)
.await
}
@@ -847,31 +834,13 @@ impl Session {
&self,
sub_id: String,
) -> Arc<TurnContext> {
let (session_configuration, turn_environments) =
self.default_turn_configuration_and_environments().await;
self.new_startup_prewarm_turn_from_configuration(
sub_id,
session_configuration,
turn_environments,
)
.await
let session_configuration = self.default_turn_configuration().await;
self.new_startup_prewarm_turn_from_configuration(sub_id, session_configuration)
.await
}
async fn default_turn_configuration_and_environments(
&self,
) -> (SessionConfiguration, ResolvedTurnEnvironments) {
let session_configuration = {
let state = self.state.lock().await;
state.session_configuration.clone()
};
let turn_environments =
match self.resolve_turn_environments(session_configuration.environment_selections()) {
Ok(turn_environments) => turn_environments,
Err(err) => {
warn!("failed to resolve stored session environments: {err}");
ResolvedTurnEnvironments::default()
}
};
(session_configuration, turn_environments)
async fn default_turn_configuration(&self) -> SessionConfiguration {
let state = self.state.lock().await;
state.session_configuration.clone()
}
}