Compare commits

...

8 Commits

Author SHA1 Message Date
Ilan Bigio
c77866cef2 Merge branch 'main' into codex/find-method-to-set-codex_session_id 2025-10-27 11:23:48 -07:00
Ilan Bigio
bae465de32 Merge branch 'main' into codex/find-method-to-set-codex_session_id
# Conflicts:
#	codex-rs/core/src/codex.rs
2025-10-27 10:52:44 -07:00
Ilan Bigio
6148019c95 Remove serial test stuff 2025-10-24 14:40:25 -07:00
Ilan Bigio
6f978a9f52 Merge branch 'main' into codex/find-method-to-set-codex_session_id 2025-10-24 11:10:34 -07:00
Ilan Bigio
c0530ebc06 Fix tests 2025-10-23 15:03:02 -07:00
Ilan Bigio
b779fb5b6d Fix unified exec session id test helper (#5535)
## Summary
- update the unified exec session-id test to reuse the existing
exec_command/write_stdin helpers instead of an undefined helper

## Testing
- not run (cargo cannot fetch tiktoken-rs in the sandbox)

------
https://chatgpt.com/codex/tasks/task_i_68f94e49ab2883289a11657a4a8287a2
2025-10-22 14:36:54 -07:00
Ilan Bigio
2ca8ea0b71 Merge branch 'main' into codex/find-method-to-set-codex_session_id 2025-10-22 10:12:47 -07:00
Ilan Bigio
3930bf6b66 Expose session id to exec environments 2025-10-21 20:13:04 -07:00
5 changed files with 85 additions and 17 deletions

View File

@@ -609,6 +609,10 @@ impl Session {
self.tx_event.clone()
}
pub(crate) fn conversation_id(&self) -> ConversationId {
self.conversation_id
}
/// Ensure all rollout writes are durably flushed.
pub(crate) async fn flush_rollout(&self) {
let recorder = {

View File

@@ -4,6 +4,8 @@ use crate::config_types::ShellEnvironmentPolicyInherit;
use std::collections::HashMap;
use std::collections::HashSet;
pub const CODEX_SESSION_ID_ENV_VAR: &str = "CODEX_SESSION_ID";
/// Construct an environment map based on the rules in the specified policy. The
/// resulting map can be passed directly to `Command::envs()` after calling
/// `env_clear()` to ensure no unintended variables are leaked to the spawned

View File

@@ -5,8 +5,10 @@ use std::sync::Arc;
use crate::apply_patch;
use crate::apply_patch::InternalApplyPatchInvocation;
use crate::apply_patch::convert_apply_patch_to_protocol;
use crate::codex::Session;
use crate::codex::TurnContext;
use crate::exec::ExecParams;
use crate::exec_env::CODEX_SESSION_ID_ENV_VAR;
use crate::exec_env::create_env;
use crate::function_tool::FunctionCallError;
use crate::tools::context::ToolInvocation;
@@ -26,12 +28,22 @@ use crate::tools::sandboxing::ToolCtx;
pub struct ShellHandler;
impl ShellHandler {
fn to_exec_params(params: ShellToolCallParams, turn_context: &TurnContext) -> ExecParams {
fn to_exec_params(
params: ShellToolCallParams,
session: &Session,
turn_context: &TurnContext,
) -> ExecParams {
let mut env = create_env(&turn_context.shell_environment_policy);
env.insert(
CODEX_SESSION_ID_ENV_VAR.to_string(),
session.conversation_id().to_string(),
);
ExecParams {
command: params.command,
cwd: turn_context.resolve_path(params.workdir.clone()),
timeout_ms: params.timeout_ms,
env: create_env(&turn_context.shell_environment_policy),
env,
with_escalated_permissions: params.with_escalated_permissions,
justification: params.justification,
arg0: None,
@@ -70,7 +82,7 @@ impl ToolHandler for ShellHandler {
"failed to parse function arguments: {e:?}"
))
})?;
let exec_params = Self::to_exec_params(params, turn.as_ref());
let exec_params = Self::to_exec_params(params, session.as_ref(), turn.as_ref());
Self::run_exec_like(
tool_name.as_str(),
exec_params,
@@ -82,7 +94,7 @@ impl ToolHandler for ShellHandler {
.await
}
ToolPayload::LocalShell { params } => {
let exec_params = Self::to_exec_params(params, turn.as_ref());
let exec_params = Self::to_exec_params(params, session.as_ref(), turn.as_ref());
Self::run_exec_like(
tool_name.as_str(),
exec_params,
@@ -104,12 +116,11 @@ impl ShellHandler {
async fn run_exec_like(
tool_name: &str,
exec_params: ExecParams,
session: Arc<crate::codex::Session>,
session: Arc<Session>,
turn: Arc<TurnContext>,
tracker: crate::tools::context::SharedTurnDiffTracker,
call_id: String,
) -> Result<ToolOutput, FunctionCallError> {
// Approval policy guard for explicit escalation in non-OnRequest modes.
if exec_params.with_escalated_permissions.unwrap_or(false)
&& !matches!(
turn.approval_policy,
@@ -122,7 +133,6 @@ impl ShellHandler {
)));
}
// Intercept apply_patch if present.
match codex_apply_patch::maybe_parse_apply_patch_verified(
&exec_params.command,
&exec_params.cwd,
@@ -132,7 +142,6 @@ impl ShellHandler {
.await
{
InternalApplyPatchInvocation::Output(item) => {
// Programmatic apply_patch path; return its result.
let content = item?;
return Ok(ToolOutput::Function {
content,
@@ -191,14 +200,10 @@ impl ShellHandler {
}
codex_apply_patch::MaybeApplyPatchVerified::ShellParseError(error) => {
tracing::trace!("Failed to parse shell command, {error:?}");
// Fall through to regular shell execution.
}
codex_apply_patch::MaybeApplyPatchVerified::NotApplyPatch => {
// Fall through to regular shell execution.
}
codex_apply_patch::MaybeApplyPatchVerified::NotApplyPatch => {}
}
// Regular shell execution path.
let emitter = ToolEmitter::shell(exec_params.command.clone(), exec_params.cwd.clone());
let event_ctx = ToolEventCtx::new(session.as_ref(), turn.as_ref(), &call_id, None);
emitter.begin(event_ctx).await;
@@ -230,3 +235,31 @@ impl ShellHandler {
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::codex::make_session_and_context;
use pretty_assertions::assert_eq;
#[test]
fn to_exec_params_includes_session_id() {
let (session, turn) = make_session_and_context();
let expected_session_id = session.conversation_id().to_string();
let params = ShellToolCallParams {
command: vec!["echo".to_string()],
workdir: None,
timeout_ms: None,
with_escalated_permissions: None,
justification: None,
};
let exec_params = ShellHandler::to_exec_params(params, &session, &turn);
assert_eq!(
exec_params.env.get(CODEX_SESSION_ID_ENV_VAR),
Some(&expected_session_id)
);
}
}

View File

@@ -321,6 +321,33 @@ mod tests {
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn unified_exec_exposes_session_id_env_var() -> anyhow::Result<()> {
skip_if_sandbox!(Ok(()));
let (session, turn) = test_session_and_turn();
let expected_session_id = session.conversation_id().to_string();
let open_shell = exec_command(&session, &turn, "bash -i", Some(2_500)).await?;
let session_id = open_shell.session_id.expect("expected session id");
let env_output = write_stdin(
&session,
session_id,
"echo $CODEX_SESSION_ID\n",
Some(2_500),
)
.await?;
assert!(
env_output.output.contains(&expected_session_id),
"shell output did not include session id: {}",
env_output.output
);
Ok(())
}
#[tokio::test]
async fn unified_exec_timeouts() -> anyhow::Result<()> {
skip_if_sandbox!(Ok(()));

View File

@@ -7,6 +7,7 @@ use tokio::time::Instant;
use crate::exec::ExecToolCallOutput;
use crate::exec::StreamOutput;
use crate::exec_env::CODEX_SESSION_ID_ENV_VAR;
use crate::exec_env::create_env;
use crate::sandboxing::ExecEnv;
use crate::tools::events::ToolEmitter;
@@ -304,11 +305,12 @@ impl UnifiedExecSessionManager {
) -> Result<UnifiedExecSession, UnifiedExecError> {
let mut orchestrator = ToolOrchestrator::new();
let mut runtime = UnifiedExecRuntime::new(self);
let req = UnifiedExecToolRequest::new(
command,
context.turn.cwd.clone(),
create_env(&context.turn.shell_environment_policy),
let mut env = create_env(&context.turn.shell_environment_policy);
env.insert(
CODEX_SESSION_ID_ENV_VAR.to_string(),
context.session.conversation_id().to_string(),
);
let req = UnifiedExecToolRequest::new(command, context.turn.cwd.clone(), env);
let tool_ctx = ToolCtx {
session: context.session.as_ref(),
turn: context.turn.as_ref(),