feat(shell-tool-mcp): add patched zsh build pipeline (#11668)

## Summary
- add `shell-tool-mcp/patches/zsh-exec-wrapper.patch` against upstream
zsh `77045ef899e53b9598bebc5a41db93a548a40ca6`
- add `zsh-linux` and `zsh-darwin` jobs to
`.github/workflows/shell-tool-mcp.yml`
- stage zsh binaries under `artifacts/vendor/<target>/zsh/<variant>/zsh`
- include zsh artifact jobs in `package.needs`
- mark staged zsh binaries executable during packaging

## Notes
- zsh source is cloned from `https://git.code.sf.net/p/zsh/code`
- workflow pins zsh commit `77045ef899e53b9598bebc5a41db93a548a40ca6`
- zsh build runs `./Util/preconfig` before `./configure`

## Validation
- parsed workflow YAML locally (`yaml-ok`)
- validated zsh patch applies cleanly with `git apply --check` on a
fresh zsh clone
This commit is contained in:
Jeremy Rose
2026-02-12 17:34:48 -08:00
committed by GitHub
parent fc073c9c5b
commit 9cf7a07281
10 changed files with 320 additions and 15 deletions

View File

@@ -33,9 +33,6 @@ pub async fn create_transport<P>(
where
P: AsRef<Path>,
{
let mcp_executable = codex_utils_cargo_bin::cargo_bin("codex-exec-mcp-server")?;
let execve_wrapper = codex_utils_cargo_bin::cargo_bin("codex-execve-wrapper")?;
// `bash` is a test resource rather than a binary target, so we must use
// `find_resource!` to locate it instead of `cargo_bin()`.
let bash = find_resource!("../suite/bash")?;
@@ -51,8 +48,24 @@ where
.await?;
assert!(status.success(), "dotslash fetch failed: {status:?}");
create_transport_with_shell_path(codex_home, dotslash_cache, bash).await
}
pub async fn create_transport_with_shell_path<P, Q, R>(
codex_home: P,
dotslash_cache: Q,
shell_path: R,
) -> anyhow::Result<TokioChildProcess>
where
P: AsRef<Path>,
Q: AsRef<Path>,
R: AsRef<Path>,
{
let mcp_executable = codex_utils_cargo_bin::cargo_bin("codex-exec-mcp-server")?;
let execve_wrapper = codex_utils_cargo_bin::cargo_bin("codex-execve-wrapper")?;
let transport = TokioChildProcess::new(Command::new(&mcp_executable).configure(|cmd| {
cmd.arg("--bash").arg(bash);
cmd.arg("--bash").arg(shell_path.as_ref());
cmd.arg("--execve").arg(&execve_wrapper);
cmd.env("CODEX_HOME", codex_home.as_ref());
cmd.env("DOTSLASH_CACHE", dotslash_cache.as_ref());

View File

@@ -10,6 +10,7 @@ use anyhow::ensure;
use codex_exec_server::ExecResult;
use exec_server_test_support::InteractiveClient;
use exec_server_test_support::create_transport;
use exec_server_test_support::create_transport_with_shell_path;
use exec_server_test_support::notify_readable_sandbox;
use exec_server_test_support::write_default_execpolicy;
use maplit::hashset;
@@ -54,7 +55,46 @@ prefix_rule(
let dotslash_cache_temp_dir = TempDir::new()?;
let dotslash_cache = dotslash_cache_temp_dir.path();
let transport = create_transport(codex_home.as_ref(), dotslash_cache).await?;
run_accept_elicitation_for_prompt_rule_with_transport(transport).await
}
/// 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.
#[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#"
# Create a rule with `decision = "prompt"` to exercise the elicitation flow.
prefix_rule(
pattern = ["git", "init"],
decision = "prompt",
match = [
"git init ."
],
)
"#,
codex_home.as_ref(),
)
.await?;
let dotslash_cache_temp_dir = TempDir::new()?;
let dotslash_cache = dotslash_cache_temp_dir.path();
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
}
async fn run_accept_elicitation_for_prompt_rule_with_transport(
transport: rmcp::transport::TokioChildProcess,
) -> Result<()> {
// Create an MCP client that approves expected elicitation messages.
let project_root = TempDir::new()?;
let project_root_path = project_root.path().canonicalize().unwrap();