mirror of
https://github.com/openai/codex.git
synced 2026-04-28 08:34:54 +00:00
test: vendor zsh fork via DotSlash and stabilize zsh-fork tests (#12518)
## Why The zsh integration tests were still brittle in two ways: - they relied on `CODEX_TEST_ZSH_PATH` / environment-specific setup, so they often did not exercise the patched zsh fork that `shell-tool-mcp` ships - once the tests consistently used the vendored zsh fork, they exposed real Linux-specific zsh-fork issues in CI In particular, the Linux failures were not just test noise: - the zsh-fork launch path was dropping `ExecRequest.arg0`, so Linux `codex-linux-sandbox` arg0 dispatch did not run and zsh wrapper-mode could receive malformed arguments - the `turn_start_shell_zsh_fork_subcommand_decline_marks_parent_declined_v2` test uses the zsh exec bridge (which talks to the parent over a Unix socket), but Linux restricted sandbox seccomp denies `connect(2)`, causing timeouts on `ubuntu-24.04` x86/arm This PR makes the zsh tests consistently run against the intended vendored zsh fork and fixes/hardens the zsh-fork path so the Linux CI signal is meaningful. ## What Changed - Added a single shared test-only DotSlash file for the patched zsh fork at `codex-rs/exec-server/tests/suite/zsh` (analogous to the existing `bash` test resource). - Updated both app-server and exec-server zsh tests to use that shared DotSlash zsh (no duplicate zsh DotSlash file, no `CODEX_TEST_ZSH_PATH` dependency). - Updated the app-server zsh-fork test helper to resolve the shared DotSlash zsh and avoid silently falling back to host zsh. - Kept the app-server zsh-fork tests configured via `config.toml`, using a test wrapper path where needed to force `zsh -df` (and rewrite `-lc` to `-c`) for the subcommand-decline test. - Hardened the app-server subcommand-decline zsh-fork test for CI variability: - tolerate an extra `/responses` POST with a no-op mock response - tolerate non-target approval ordering while remaining strict on the two `/usr/bin/true` approvals and decline behavior - use `DangerFullAccess` on Linux for this one test because it validates zsh approval flow, not Linux sandbox socket restrictions - Fixed zsh-fork process launching on Linux by preserving `req.arg0` in `ZshExecBridge::execute_shell_request(...)` so `codex-linux-sandbox` arg0 dispatch continues to work. - Moved `maybe_run_zsh_exec_wrapper_mode()` under `arg0_dispatch_or_else(...)` in `app-server` and `cli` so wrapper-mode handling coexists correctly with arg0-dispatched helper modes. - Consolidated duplicated `dotslash -- fetch` resolution logic into shared test support (`core/tests/common/lib.rs`). - Updated `codex-rs/exec-server/tests/suite/accept_elicitation.rs` to use DotSlash zsh and hardened the zsh elicitation test for Bazel/zsh differences by: - resolving an absolute `git` path - running `git init --quiet .` - asserting success / `.git` creation instead of relying on banner text ## Verification - `cargo test -p codex-app-server turn_start_zsh_fork -- --nocapture` - `cargo test -p codex-exec-server accept_elicitation -- --nocapture` - `bazel test //codex-rs/exec-server:exec-server-all-test --test_output=streamed --test_arg=--nocapture --test_arg=accept_elicitation_for_prompt_rule_with_zsh` - CI (`rust-ci`) on the final cleaned commit: `Tests — ubuntu-24.04 - x86_64-unknown-linux-gnu` and `Tests — ubuntu-24.04-arm - aarch64-unknown-linux-gnu` passed in [run 22291424358](https://github.com/openai/codex/actions/runs/22291424358)
This commit is contained in:
@@ -61,15 +61,9 @@ prefix_rule(
|
||||
/// Verify the same prompt/escalation flow works when the server is launched
|
||||
/// with a patched zsh binary.
|
||||
///
|
||||
/// Set CODEX_TEST_ZSH_PATH to enable this test locally or in CI.
|
||||
/// The suite resolves `tests/suite/zsh` via DotSlash on first use.
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn accept_elicitation_for_prompt_rule_with_zsh() -> Result<()> {
|
||||
let Some(zsh_path) = std::env::var_os("CODEX_TEST_ZSH_PATH") else {
|
||||
eprintln!("skipping zsh test: CODEX_TEST_ZSH_PATH is not set");
|
||||
return Ok(());
|
||||
};
|
||||
let zsh_path = PathBuf::from(zsh_path);
|
||||
|
||||
let codex_home = TempDir::new()?;
|
||||
write_default_execpolicy(
|
||||
r#"
|
||||
@@ -87,6 +81,11 @@ prefix_rule(
|
||||
.await?;
|
||||
let dotslash_cache_temp_dir = TempDir::new()?;
|
||||
let dotslash_cache = dotslash_cache_temp_dir.path();
|
||||
let zsh_path = resolve_test_zsh_path(dotslash_cache).await?;
|
||||
eprintln!(
|
||||
"using zsh path for exec-server test: {}",
|
||||
zsh_path.display()
|
||||
);
|
||||
let transport =
|
||||
create_transport_with_shell_path(codex_home.as_ref(), dotslash_cache, &zsh_path).await?;
|
||||
run_accept_elicitation_for_prompt_rule_with_transport(transport).await
|
||||
@@ -95,13 +94,13 @@ prefix_rule(
|
||||
async fn run_accept_elicitation_for_prompt_rule_with_transport(
|
||||
transport: rmcp::transport::TokioChildProcess,
|
||||
) -> Result<()> {
|
||||
// Create an MCP client that approves expected elicitation messages.
|
||||
// Create an MCP client that approves the expected elicitation message.
|
||||
let project_root = TempDir::new()?;
|
||||
let project_root_path = project_root.path().canonicalize().unwrap();
|
||||
let git_path = resolve_git_path(USE_LOGIN_SHELL).await?;
|
||||
let git_init_command = format!("{git_path} init --quiet .");
|
||||
let expected_elicitation_message = format!(
|
||||
"Allow agent to run `{} init .` in `{}`?",
|
||||
git_path,
|
||||
"Allow agent to run `{git_path} init --quiet .` in `{}`?",
|
||||
project_root_path.display()
|
||||
);
|
||||
let elicitation_requests: Arc<Mutex<Vec<CreateElicitationRequestParams>>> = Default::default();
|
||||
@@ -142,7 +141,7 @@ async fn run_accept_elicitation_for_prompt_rule_with_transport(
|
||||
arguments: Some(object(json!(
|
||||
{
|
||||
"login": USE_LOGIN_SHELL,
|
||||
"command": "git init .",
|
||||
"command": git_init_command,
|
||||
"workdir": project_root_path.to_string_lossy(),
|
||||
}
|
||||
))),
|
||||
@@ -157,15 +156,11 @@ async fn run_accept_elicitation_for_prompt_rule_with_transport(
|
||||
let ExecResult {
|
||||
exit_code, output, ..
|
||||
} = serde_json::from_str::<ExecResult>(&tool_call_content.text)?;
|
||||
let git_init_succeeded = format!(
|
||||
"Initialized empty Git repository in {}/.git/\n",
|
||||
project_root_path.display()
|
||||
);
|
||||
// Normally, this would be an exact match, but it might include extra output
|
||||
// if `git config set advice.defaultBranchName false` has not been set.
|
||||
// `git init --quiet` is expected to suppress the usual initialization
|
||||
// banner, so assert on success and filesystem effects instead of output.
|
||||
assert!(
|
||||
output.contains(&git_init_succeeded),
|
||||
"expected output `{output}` to contain `{git_init_succeeded}`"
|
||||
output.is_empty(),
|
||||
"expected no output from `git init --quiet .`, got `{output}`"
|
||||
);
|
||||
assert_eq!(exit_code, 0, "command should succeed");
|
||||
assert_eq!(is_error, Some(false), "command should succeed");
|
||||
@@ -192,6 +187,12 @@ async fn run_accept_elicitation_for_prompt_rule_with_transport(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn resolve_test_zsh_path(dotslash_cache: &std::path::Path) -> Result<PathBuf> {
|
||||
let dotslash_zsh = codex_utils_cargo_bin::find_resource!("tests/suite/zsh")?;
|
||||
core_test_support::fetch_dotslash_file(&dotslash_zsh, Some(dotslash_cache))
|
||||
.with_context(|| format!("failed to fetch test zsh from {}", dotslash_zsh.display()))
|
||||
}
|
||||
|
||||
fn ensure_codex_cli() -> Result<PathBuf> {
|
||||
let codex_cli = codex_utils_cargo_bin::cargo_bin("codex")?;
|
||||
|
||||
|
||||
72
codex-rs/exec-server/tests/suite/zsh
Executable file
72
codex-rs/exec-server/tests/suite/zsh
Executable file
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env dotslash
|
||||
|
||||
// This is the patched zsh fork built by
|
||||
// `.github/workflows/shell-tool-mcp.yml` for the shell-tool-mcp package.
|
||||
// Fetching the prebuilt version via DotSlash makes it easier to write
|
||||
// integration tests that exercise the zsh fork behavior in exec-server tests.
|
||||
//
|
||||
// TODO(mbolin): Currently, we use a .tgz artifact that includes binaries for
|
||||
// multiple platforms, but we could save a bit of space by making arch-specific
|
||||
// artifacts available in the GitHub releases and referencing those here.
|
||||
{
|
||||
"name": "codex-zsh",
|
||||
"platforms": {
|
||||
// macOS 13 builds (and therefore x86_64) were dropped in
|
||||
// https://github.com/openai/codex/pull/7295, so we only provide an
|
||||
// Apple Silicon build for now.
|
||||
"macos-aarch64": {
|
||||
"size": 53771483,
|
||||
"hash": "blake3",
|
||||
"digest": "ff664f63f5e1fa62762c9aff0aafa66cf196faf9b157f98ec98f59c152fc7bd3",
|
||||
"format": "tar.gz",
|
||||
"path": "package/vendor/aarch64-apple-darwin/zsh/macos-15/zsh",
|
||||
"providers": [
|
||||
{
|
||||
"url": "https://github.com/openai/codex/releases/download/rust-v0.104.0/codex-shell-tool-mcp-npm-0.104.0.tgz"
|
||||
},
|
||||
{
|
||||
"type": "github-release",
|
||||
"repo": "openai/codex",
|
||||
"tag": "rust-v0.104.0",
|
||||
"name": "codex-shell-tool-mcp-npm-0.104.0.tgz"
|
||||
}
|
||||
]
|
||||
},
|
||||
"linux-x86_64": {
|
||||
"size": 53771483,
|
||||
"hash": "blake3",
|
||||
"digest": "ff664f63f5e1fa62762c9aff0aafa66cf196faf9b157f98ec98f59c152fc7bd3",
|
||||
"format": "tar.gz",
|
||||
"path": "package/vendor/x86_64-unknown-linux-musl/zsh/ubuntu-24.04/zsh",
|
||||
"providers": [
|
||||
{
|
||||
"url": "https://github.com/openai/codex/releases/download/rust-v0.104.0/codex-shell-tool-mcp-npm-0.104.0.tgz"
|
||||
},
|
||||
{
|
||||
"type": "github-release",
|
||||
"repo": "openai/codex",
|
||||
"tag": "rust-v0.104.0",
|
||||
"name": "codex-shell-tool-mcp-npm-0.104.0.tgz"
|
||||
}
|
||||
]
|
||||
},
|
||||
"linux-aarch64": {
|
||||
"size": 53771483,
|
||||
"hash": "blake3",
|
||||
"digest": "ff664f63f5e1fa62762c9aff0aafa66cf196faf9b157f98ec98f59c152fc7bd3",
|
||||
"format": "tar.gz",
|
||||
"path": "package/vendor/aarch64-unknown-linux-musl/zsh/ubuntu-24.04/zsh",
|
||||
"providers": [
|
||||
{
|
||||
"url": "https://github.com/openai/codex/releases/download/rust-v0.104.0/codex-shell-tool-mcp-npm-0.104.0.tgz"
|
||||
},
|
||||
{
|
||||
"type": "github-release",
|
||||
"repo": "openai/codex",
|
||||
"tag": "rust-v0.104.0",
|
||||
"name": "codex-shell-tool-mcp-npm-0.104.0.tgz"
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user