From d337b51741bfb6b0caa452146535582e7a5cddda Mon Sep 17 00:00:00 2001 From: jif-oai Date: Thu, 5 Feb 2026 15:49:57 +0000 Subject: [PATCH] feat: wire ephemeral in `codex exec` (#10758) --- codex-rs/README.md | 1 + codex-rs/exec/src/cli.rs | 6 +++ codex-rs/exec/src/lib.rs | 3 +- codex-rs/exec/tests/suite/ephemeral.rs | 55 ++++++++++++++++++++++++++ codex-rs/exec/tests/suite/mod.rs | 1 + 5 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 codex-rs/exec/tests/suite/ephemeral.rs diff --git a/codex-rs/README.md b/codex-rs/README.md index cbe1fe377..9cdd772b2 100644 --- a/codex-rs/README.md +++ b/codex-rs/README.md @@ -51,6 +51,7 @@ You can enable notifications by configuring a script that is run whenever the ag ### `codex exec` to run Codex programmatically/non-interactively To run Codex non-interactively, run `codex exec PROMPT` (you can also pass the prompt via `stdin`) and Codex will work on your task until it decides that it is done and exits. Output is printed to the terminal directly. You can set the `RUST_LOG` environment variable to see more about what's going on. +Use `codex exec --ephemeral ...` to run without persisting session rollout files to disk. ### Experimenting with the Codex Sandbox diff --git a/codex-rs/exec/src/cli.rs b/codex-rs/exec/src/cli.rs index c27c39f01..5d7a9ba72 100644 --- a/codex-rs/exec/src/cli.rs +++ b/codex-rs/exec/src/cli.rs @@ -71,6 +71,10 @@ pub struct Cli { #[arg(long = "add-dir", value_name = "DIR", value_hint = clap::ValueHint::DirPath)] pub add_dir: Vec, + /// Run without persisting session files to disk. + #[arg(long = "ephemeral", global = true, default_value_t = false)] + pub ephemeral: bool, + /// Path to a JSON Schema file describing the model's final response shape. #[arg(long = "output-schema", value_name = "FILE")] pub output_schema: Option, @@ -262,9 +266,11 @@ mod tests { "gpt-5.2-codex", "--dangerously-bypass-approvals-and-sandbox", "--skip-git-repo-check", + "--ephemeral", PROMPT, ]); + assert!(cli.ephemeral); let Some(Command::Resume(args)) = cli.command else { panic!("expected resume command"); }; diff --git a/codex-rs/exec/src/lib.rs b/codex-rs/exec/src/lib.rs index a6ded3927..0f8e8ad72 100644 --- a/codex-rs/exec/src/lib.rs +++ b/codex-rs/exec/src/lib.rs @@ -103,6 +103,7 @@ pub async fn run_main(cli: Cli, codex_linux_sandbox_exe: Option) -> any cwd, skip_git_repo_check, add_dir, + ephemeral, color, last_message_file, json: json_mode, @@ -254,7 +255,7 @@ pub async fn run_main(cli: Cli, codex_linux_sandbox_exe: Option) -> any include_apply_patch_tool: None, show_raw_agent_reasoning: oss.then_some(true), tools_web_search_request: None, - ephemeral: None, + ephemeral: ephemeral.then_some(true), additional_writable_roots: add_dir, }; diff --git a/codex-rs/exec/tests/suite/ephemeral.rs b/codex-rs/exec/tests/suite/ephemeral.rs new file mode 100644 index 000000000..533a732f9 --- /dev/null +++ b/codex-rs/exec/tests/suite/ephemeral.rs @@ -0,0 +1,55 @@ +#![cfg(not(target_os = "windows"))] +#![allow(clippy::expect_used, clippy::unwrap_used)] + +use codex_utils_cargo_bin::find_resource; +use core_test_support::test_codex_exec::test_codex_exec; +use walkdir::WalkDir; + +fn session_rollout_count(home_path: &std::path::Path) -> usize { + let sessions_dir = home_path.join("sessions"); + if !sessions_dir.exists() { + return 0; + } + + WalkDir::new(sessions_dir) + .into_iter() + .filter_map(Result::ok) + .filter(|entry| entry.file_type().is_file()) + .filter(|entry| entry.file_name().to_string_lossy().ends_with(".jsonl")) + .count() +} + +#[test] +fn persists_rollout_file_by_default() -> anyhow::Result<()> { + let test = test_codex_exec(); + let fixture = find_resource!("tests/fixtures/cli_responses_fixture.sse")?; + + test.cmd() + .env("CODEX_RS_SSE_FIXTURE", &fixture) + .env("OPENAI_BASE_URL", "http://unused.local") + .arg("--skip-git-repo-check") + .arg("default persistence behavior") + .assert() + .code(0); + + assert_eq!(session_rollout_count(test.home_path()), 1); + Ok(()) +} + +#[test] +fn does_not_persist_rollout_file_in_ephemeral_mode() -> anyhow::Result<()> { + let test = test_codex_exec(); + let fixture = find_resource!("tests/fixtures/cli_responses_fixture.sse")?; + + test.cmd() + .env("CODEX_RS_SSE_FIXTURE", &fixture) + .env("OPENAI_BASE_URL", "http://unused.local") + .arg("--skip-git-repo-check") + .arg("--ephemeral") + .arg("ephemeral behavior") + .assert() + .code(0); + + assert_eq!(session_rollout_count(test.home_path()), 0); + Ok(()) +} diff --git a/codex-rs/exec/tests/suite/mod.rs b/codex-rs/exec/tests/suite/mod.rs index 77012ee3b..dbfc966df 100644 --- a/codex-rs/exec/tests/suite/mod.rs +++ b/codex-rs/exec/tests/suite/mod.rs @@ -2,6 +2,7 @@ mod add_dir; mod apply_patch; mod auth_env; +mod ephemeral; mod originator; mod output_schema; mod resume;