mirror of
https://github.com/openai/codex.git
synced 2026-03-05 21:45:28 +03:00
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:
211
.github/workflows/shell-tool-mcp.yml
vendored
211
.github/workflows/shell-tool-mcp.yml
vendored
@@ -329,6 +329,212 @@ jobs:
|
|||||||
path: artifacts/**
|
path: artifacts/**
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
|
zsh-linux:
|
||||||
|
name: Build zsh (Linux) - ${{ matrix.variant }} - ${{ matrix.target }}
|
||||||
|
needs: metadata
|
||||||
|
runs-on: ${{ matrix.runner }}
|
||||||
|
timeout-minutes: 30
|
||||||
|
container:
|
||||||
|
image: ${{ matrix.image }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- runner: ubuntu-24.04
|
||||||
|
target: x86_64-unknown-linux-musl
|
||||||
|
variant: ubuntu-24.04
|
||||||
|
image: ubuntu:24.04
|
||||||
|
- runner: ubuntu-24.04
|
||||||
|
target: x86_64-unknown-linux-musl
|
||||||
|
variant: ubuntu-22.04
|
||||||
|
image: ubuntu:22.04
|
||||||
|
- runner: ubuntu-24.04
|
||||||
|
target: x86_64-unknown-linux-musl
|
||||||
|
variant: debian-12
|
||||||
|
image: debian:12
|
||||||
|
- runner: ubuntu-24.04
|
||||||
|
target: x86_64-unknown-linux-musl
|
||||||
|
variant: debian-11
|
||||||
|
image: debian:11
|
||||||
|
- runner: ubuntu-24.04
|
||||||
|
target: x86_64-unknown-linux-musl
|
||||||
|
variant: centos-9
|
||||||
|
image: quay.io/centos/centos:stream9
|
||||||
|
- runner: ubuntu-24.04-arm
|
||||||
|
target: aarch64-unknown-linux-musl
|
||||||
|
variant: ubuntu-24.04
|
||||||
|
image: arm64v8/ubuntu:24.04
|
||||||
|
- runner: ubuntu-24.04-arm
|
||||||
|
target: aarch64-unknown-linux-musl
|
||||||
|
variant: ubuntu-22.04
|
||||||
|
image: arm64v8/ubuntu:22.04
|
||||||
|
- runner: ubuntu-24.04-arm
|
||||||
|
target: aarch64-unknown-linux-musl
|
||||||
|
variant: ubuntu-20.04
|
||||||
|
image: arm64v8/ubuntu:20.04
|
||||||
|
- runner: ubuntu-24.04-arm
|
||||||
|
target: aarch64-unknown-linux-musl
|
||||||
|
variant: debian-12
|
||||||
|
image: arm64v8/debian:12
|
||||||
|
- runner: ubuntu-24.04-arm
|
||||||
|
target: aarch64-unknown-linux-musl
|
||||||
|
variant: debian-11
|
||||||
|
image: arm64v8/debian:11
|
||||||
|
- runner: ubuntu-24.04-arm
|
||||||
|
target: aarch64-unknown-linux-musl
|
||||||
|
variant: centos-9
|
||||||
|
image: quay.io/centos/centos:stream9
|
||||||
|
steps:
|
||||||
|
- name: Install build prerequisites
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
if command -v apt-get >/dev/null 2>&1; then
|
||||||
|
apt-get update
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get install -y git build-essential bison autoconf gettext
|
||||||
|
elif command -v dnf >/dev/null 2>&1; then
|
||||||
|
dnf install -y git gcc gcc-c++ make bison autoconf gettext
|
||||||
|
elif command -v yum >/dev/null 2>&1; then
|
||||||
|
yum install -y git gcc gcc-c++ make bison autoconf gettext
|
||||||
|
else
|
||||||
|
echo "Unsupported package manager in container"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Build patched zsh
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
git clone --depth 1 https://git.code.sf.net/p/zsh/code /tmp/zsh
|
||||||
|
cd /tmp/zsh
|
||||||
|
git fetch --depth 1 origin 77045ef899e53b9598bebc5a41db93a548a40ca6
|
||||||
|
git checkout 77045ef899e53b9598bebc5a41db93a548a40ca6
|
||||||
|
git apply "${GITHUB_WORKSPACE}/shell-tool-mcp/patches/zsh-exec-wrapper.patch"
|
||||||
|
./Util/preconfig
|
||||||
|
./configure
|
||||||
|
cores="$(command -v nproc >/dev/null 2>&1 && nproc || getconf _NPROCESSORS_ONLN)"
|
||||||
|
make -j"${cores}"
|
||||||
|
|
||||||
|
dest="${GITHUB_WORKSPACE}/artifacts/vendor/${{ matrix.target }}/zsh/${{ matrix.variant }}"
|
||||||
|
mkdir -p "$dest"
|
||||||
|
cp Src/zsh "$dest/zsh"
|
||||||
|
|
||||||
|
- name: Smoke test zsh exec wrapper
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
tmpdir="$(mktemp -d)"
|
||||||
|
cat > "$tmpdir/exec-wrapper" <<'EOF'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
: "${CODEX_WRAPPER_LOG:?missing CODEX_WRAPPER_LOG}"
|
||||||
|
printf '%s\n' "$@" > "$CODEX_WRAPPER_LOG"
|
||||||
|
file="$1"
|
||||||
|
shift
|
||||||
|
if [[ "$#" -eq 0 ]]; then
|
||||||
|
exec "$file"
|
||||||
|
fi
|
||||||
|
arg0="$1"
|
||||||
|
shift
|
||||||
|
exec -a "$arg0" "$file" "$@"
|
||||||
|
EOF
|
||||||
|
chmod +x "$tmpdir/exec-wrapper"
|
||||||
|
|
||||||
|
CODEX_WRAPPER_LOG="$tmpdir/wrapper.log" \
|
||||||
|
EXEC_WRAPPER="$tmpdir/exec-wrapper" \
|
||||||
|
/tmp/zsh/Src/zsh -fc '/bin/echo smoke-zsh' > "$tmpdir/stdout.txt"
|
||||||
|
|
||||||
|
grep -Fx "smoke-zsh" "$tmpdir/stdout.txt"
|
||||||
|
grep -Fx "/bin/echo" "$tmpdir/wrapper.log"
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v6
|
||||||
|
with:
|
||||||
|
name: shell-tool-mcp-zsh-${{ matrix.target }}-${{ matrix.variant }}
|
||||||
|
path: artifacts/**
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
zsh-darwin:
|
||||||
|
name: Build zsh (macOS) - ${{ matrix.variant }} - ${{ matrix.target }}
|
||||||
|
needs: metadata
|
||||||
|
runs-on: ${{ matrix.runner }}
|
||||||
|
timeout-minutes: 30
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- runner: macos-15-xlarge
|
||||||
|
target: aarch64-apple-darwin
|
||||||
|
variant: macos-15
|
||||||
|
- runner: macos-14
|
||||||
|
target: aarch64-apple-darwin
|
||||||
|
variant: macos-14
|
||||||
|
steps:
|
||||||
|
- name: Install build prerequisites
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
if ! command -v autoconf >/dev/null 2>&1; then
|
||||||
|
brew install autoconf
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Build patched zsh
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
git clone --depth 1 https://git.code.sf.net/p/zsh/code /tmp/zsh
|
||||||
|
cd /tmp/zsh
|
||||||
|
git fetch --depth 1 origin 77045ef899e53b9598bebc5a41db93a548a40ca6
|
||||||
|
git checkout 77045ef899e53b9598bebc5a41db93a548a40ca6
|
||||||
|
git apply "${GITHUB_WORKSPACE}/shell-tool-mcp/patches/zsh-exec-wrapper.patch"
|
||||||
|
./Util/preconfig
|
||||||
|
./configure
|
||||||
|
cores="$(getconf _NPROCESSORS_ONLN)"
|
||||||
|
make -j"${cores}"
|
||||||
|
|
||||||
|
dest="${GITHUB_WORKSPACE}/artifacts/vendor/${{ matrix.target }}/zsh/${{ matrix.variant }}"
|
||||||
|
mkdir -p "$dest"
|
||||||
|
cp Src/zsh "$dest/zsh"
|
||||||
|
|
||||||
|
- name: Smoke test zsh exec wrapper
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
tmpdir="$(mktemp -d)"
|
||||||
|
cat > "$tmpdir/exec-wrapper" <<'EOF'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
: "${CODEX_WRAPPER_LOG:?missing CODEX_WRAPPER_LOG}"
|
||||||
|
printf '%s\n' "$@" > "$CODEX_WRAPPER_LOG"
|
||||||
|
file="$1"
|
||||||
|
shift
|
||||||
|
if [[ "$#" -eq 0 ]]; then
|
||||||
|
exec "$file"
|
||||||
|
fi
|
||||||
|
arg0="$1"
|
||||||
|
shift
|
||||||
|
exec -a "$arg0" "$file" "$@"
|
||||||
|
EOF
|
||||||
|
chmod +x "$tmpdir/exec-wrapper"
|
||||||
|
|
||||||
|
CODEX_WRAPPER_LOG="$tmpdir/wrapper.log" \
|
||||||
|
EXEC_WRAPPER="$tmpdir/exec-wrapper" \
|
||||||
|
/tmp/zsh/Src/zsh -fc '/bin/echo smoke-zsh' > "$tmpdir/stdout.txt"
|
||||||
|
|
||||||
|
grep -Fx "smoke-zsh" "$tmpdir/stdout.txt"
|
||||||
|
grep -Fx "/bin/echo" "$tmpdir/wrapper.log"
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v6
|
||||||
|
with:
|
||||||
|
name: shell-tool-mcp-zsh-${{ matrix.target }}-${{ matrix.variant }}
|
||||||
|
path: artifacts/**
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
package:
|
package:
|
||||||
name: Package npm module
|
name: Package npm module
|
||||||
needs:
|
needs:
|
||||||
@@ -336,6 +542,8 @@ jobs:
|
|||||||
- rust-binaries
|
- rust-binaries
|
||||||
- bash-linux
|
- bash-linux
|
||||||
- bash-darwin
|
- bash-darwin
|
||||||
|
- zsh-linux
|
||||||
|
- zsh-darwin
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
PACKAGE_VERSION: ${{ needs.metadata.outputs.version }}
|
PACKAGE_VERSION: ${{ needs.metadata.outputs.version }}
|
||||||
@@ -409,7 +617,8 @@ jobs:
|
|||||||
chmod +x \
|
chmod +x \
|
||||||
"$staging"/vendor/*/codex-exec-mcp-server \
|
"$staging"/vendor/*/codex-exec-mcp-server \
|
||||||
"$staging"/vendor/*/codex-execve-wrapper \
|
"$staging"/vendor/*/codex-execve-wrapper \
|
||||||
"$staging"/vendor/*/bash/*/bash
|
"$staging"/vendor/*/bash/*/bash \
|
||||||
|
"$staging"/vendor/*/zsh/*/zsh
|
||||||
|
|
||||||
- name: Create npm tarball
|
- name: Create npm tarball
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
This crate contains the code for two executables:
|
This crate contains the code for two executables:
|
||||||
|
|
||||||
- `codex-exec-mcp-server` is an MCP server that provides a tool named `shell` that runs a shell command inside a sandboxed instance of Bash. Every resulting `execve(2)` call made within Bash is intercepted and run via the executable defined by the `BASH_EXEC_WRAPPER` environment variable within the Bash process. In practice, `BASH_EXEC_WRAPPER` is set to `codex-execve-wrapper`.
|
- `codex-exec-mcp-server` is an MCP server that provides a tool named `shell` that runs a shell command inside a sandboxed shell process. Every resulting `execve(2)` call made within that shell is intercepted and run via the executable defined by the `EXEC_WRAPPER` environment variable within the shell process. In practice, `EXEC_WRAPPER` is set to `codex-execve-wrapper`.
|
||||||
- `codex-execve-wrapper` is the executable that takes the arguments to the `execve(2)` call and "escalates" it to the MCP server via a shared file descriptor (specified by the `CODEX_ESCALATE_SOCKET` environment variable) for consideration. Based on the [Codex `.rules`](https://developers.openai.com/codex/local-config#rules-preview), the MCP server replies with one of:
|
- `codex-execve-wrapper` is the executable that takes the arguments to the `execve(2)` call and "escalates" it to the MCP server via a shared file descriptor (specified by the `CODEX_ESCALATE_SOCKET` environment variable) for consideration. Based on the [Codex `.rules`](https://developers.openai.com/codex/local-config#rules-preview), the MCP server replies with one of:
|
||||||
- `Run`: `codex-execve-wrapper` should invoke `execve(2)` on itself to run the original command within Bash
|
- `Run`: `codex-execve-wrapper` should invoke `execve(2)` on itself to run the original command within Bash
|
||||||
- `Escalate`: forward the file descriptors of the current process to the MCP server so the command can be run faithfully outside the sandbox. Because the MCP server will have the original FDs for `stdout` and `stderr`, it can write those directly. When the process completes, the MCP server forwards the exit code to `codex-execve-wrapper` so that it exits in a consistent manner.
|
- `Escalate`: forward the file descriptors of the current process to the MCP server so the command can be run faithfully outside the sandbox. Because the MCP server will have the original FDs for `stdout` and `stderr`, it can write those directly. When the process completes, the MCP server forwards the exit code to `codex-execve-wrapper` so that it exits in a consistent manner.
|
||||||
@@ -10,7 +10,7 @@ This crate contains the code for two executables:
|
|||||||
|
|
||||||
## Patched Bash
|
## Patched Bash
|
||||||
|
|
||||||
We carry a small patch to `execute_cmd.c` (see `patches/bash-exec-wrapper.patch`) that adds support for `BASH_EXEC_WRAPPER`. The original commit message is “add support for BASH_EXEC_WRAPPER” and the patch applies cleanly to `a8a1c2fac029404d3f42cd39f5a20f24b6e4fe4b` from https://github.com/bminor/bash. To rebuild manually:
|
We carry a small patch to `execute_cmd.c` (see `patches/bash-exec-wrapper.patch`) that adds support for `EXEC_WRAPPER`. The original commit message is “add support for BASH_EXEC_WRAPPER” and the patch applies cleanly to `a8a1c2fac029404d3f42cd39f5a20f24b6e4fe4b` from https://github.com/bminor/bash. To rebuild manually:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/bminor/bash
|
git clone https://github.com/bminor/bash
|
||||||
|
|||||||
@@ -5,11 +5,12 @@ use std::os::fd::OwnedFd;
|
|||||||
|
|
||||||
use anyhow::Context as _;
|
use anyhow::Context as _;
|
||||||
|
|
||||||
use crate::posix::escalate_protocol::BASH_EXEC_WRAPPER_ENV_VAR;
|
|
||||||
use crate::posix::escalate_protocol::ESCALATE_SOCKET_ENV_VAR;
|
use crate::posix::escalate_protocol::ESCALATE_SOCKET_ENV_VAR;
|
||||||
|
use crate::posix::escalate_protocol::EXEC_WRAPPER_ENV_VAR;
|
||||||
use crate::posix::escalate_protocol::EscalateAction;
|
use crate::posix::escalate_protocol::EscalateAction;
|
||||||
use crate::posix::escalate_protocol::EscalateRequest;
|
use crate::posix::escalate_protocol::EscalateRequest;
|
||||||
use crate::posix::escalate_protocol::EscalateResponse;
|
use crate::posix::escalate_protocol::EscalateResponse;
|
||||||
|
use crate::posix::escalate_protocol::LEGACY_BASH_EXEC_WRAPPER_ENV_VAR;
|
||||||
use crate::posix::escalate_protocol::SuperExecMessage;
|
use crate::posix::escalate_protocol::SuperExecMessage;
|
||||||
use crate::posix::escalate_protocol::SuperExecResult;
|
use crate::posix::escalate_protocol::SuperExecResult;
|
||||||
use crate::posix::socket::AsyncDatagramSocket;
|
use crate::posix::socket::AsyncDatagramSocket;
|
||||||
@@ -38,7 +39,7 @@ pub(crate) async fn run(file: String, argv: Vec<String>) -> anyhow::Result<i32>
|
|||||||
.filter(|(k, _)| {
|
.filter(|(k, _)| {
|
||||||
!matches!(
|
!matches!(
|
||||||
k.as_str(),
|
k.as_str(),
|
||||||
ESCALATE_SOCKET_ENV_VAR | BASH_EXEC_WRAPPER_ENV_VAR
|
ESCALATE_SOCKET_ENV_VAR | EXEC_WRAPPER_ENV_VAR | LEGACY_BASH_EXEC_WRAPPER_ENV_VAR
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|||||||
@@ -8,8 +8,11 @@ use serde::Serialize;
|
|||||||
/// 'exec-server escalate' reads this to find the inherited FD for the escalate socket.
|
/// 'exec-server escalate' reads this to find the inherited FD for the escalate socket.
|
||||||
pub(super) const ESCALATE_SOCKET_ENV_VAR: &str = "CODEX_ESCALATE_SOCKET";
|
pub(super) const ESCALATE_SOCKET_ENV_VAR: &str = "CODEX_ESCALATE_SOCKET";
|
||||||
|
|
||||||
/// The patched bash uses this to wrap exec() calls.
|
/// Patched shells use this to wrap exec() calls.
|
||||||
pub(super) const BASH_EXEC_WRAPPER_ENV_VAR: &str = "BASH_EXEC_WRAPPER";
|
pub(super) const EXEC_WRAPPER_ENV_VAR: &str = "EXEC_WRAPPER";
|
||||||
|
|
||||||
|
/// Compatibility alias for older patched bash builds.
|
||||||
|
pub(super) const LEGACY_BASH_EXEC_WRAPPER_ENV_VAR: &str = "BASH_EXEC_WRAPPER";
|
||||||
|
|
||||||
/// The client sends this to the server to request an exec() call.
|
/// The client sends this to the server to request an exec() call.
|
||||||
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
|
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
|
||||||
|
|||||||
@@ -15,11 +15,12 @@ use codex_core::sandboxing::SandboxPermissions;
|
|||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
use crate::posix::escalate_protocol::BASH_EXEC_WRAPPER_ENV_VAR;
|
|
||||||
use crate::posix::escalate_protocol::ESCALATE_SOCKET_ENV_VAR;
|
use crate::posix::escalate_protocol::ESCALATE_SOCKET_ENV_VAR;
|
||||||
|
use crate::posix::escalate_protocol::EXEC_WRAPPER_ENV_VAR;
|
||||||
use crate::posix::escalate_protocol::EscalateAction;
|
use crate::posix::escalate_protocol::EscalateAction;
|
||||||
use crate::posix::escalate_protocol::EscalateRequest;
|
use crate::posix::escalate_protocol::EscalateRequest;
|
||||||
use crate::posix::escalate_protocol::EscalateResponse;
|
use crate::posix::escalate_protocol::EscalateResponse;
|
||||||
|
use crate::posix::escalate_protocol::LEGACY_BASH_EXEC_WRAPPER_ENV_VAR;
|
||||||
use crate::posix::escalate_protocol::SuperExecMessage;
|
use crate::posix::escalate_protocol::SuperExecMessage;
|
||||||
use crate::posix::escalate_protocol::SuperExecResult;
|
use crate::posix::escalate_protocol::SuperExecResult;
|
||||||
use crate::posix::escalation_policy::EscalationPolicy;
|
use crate::posix::escalation_policy::EscalationPolicy;
|
||||||
@@ -63,7 +64,11 @@ impl EscalateServer {
|
|||||||
client_socket.as_raw_fd().to_string(),
|
client_socket.as_raw_fd().to_string(),
|
||||||
);
|
);
|
||||||
env.insert(
|
env.insert(
|
||||||
BASH_EXEC_WRAPPER_ENV_VAR.to_string(),
|
EXEC_WRAPPER_ENV_VAR.to_string(),
|
||||||
|
self.execve_wrapper.to_string_lossy().to_string(),
|
||||||
|
);
|
||||||
|
env.insert(
|
||||||
|
LEGACY_BASH_EXEC_WRAPPER_ENV_VAR.to_string(),
|
||||||
self.execve_wrapper.to_string_lossy().to_string(),
|
self.execve_wrapper.to_string_lossy().to_string(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -33,9 +33,6 @@ pub async fn create_transport<P>(
|
|||||||
where
|
where
|
||||||
P: AsRef<Path>,
|
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
|
// `bash` is a test resource rather than a binary target, so we must use
|
||||||
// `find_resource!` to locate it instead of `cargo_bin()`.
|
// `find_resource!` to locate it instead of `cargo_bin()`.
|
||||||
let bash = find_resource!("../suite/bash")?;
|
let bash = find_resource!("../suite/bash")?;
|
||||||
@@ -51,8 +48,24 @@ where
|
|||||||
.await?;
|
.await?;
|
||||||
assert!(status.success(), "dotslash fetch failed: {status:?}");
|
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| {
|
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.arg("--execve").arg(&execve_wrapper);
|
||||||
cmd.env("CODEX_HOME", codex_home.as_ref());
|
cmd.env("CODEX_HOME", codex_home.as_ref());
|
||||||
cmd.env("DOTSLASH_CACHE", dotslash_cache.as_ref());
|
cmd.env("DOTSLASH_CACHE", dotslash_cache.as_ref());
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use anyhow::ensure;
|
|||||||
use codex_exec_server::ExecResult;
|
use codex_exec_server::ExecResult;
|
||||||
use exec_server_test_support::InteractiveClient;
|
use exec_server_test_support::InteractiveClient;
|
||||||
use exec_server_test_support::create_transport;
|
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::notify_readable_sandbox;
|
||||||
use exec_server_test_support::write_default_execpolicy;
|
use exec_server_test_support::write_default_execpolicy;
|
||||||
use maplit::hashset;
|
use maplit::hashset;
|
||||||
@@ -54,7 +55,46 @@ prefix_rule(
|
|||||||
let dotslash_cache_temp_dir = TempDir::new()?;
|
let dotslash_cache_temp_dir = TempDir::new()?;
|
||||||
let dotslash_cache = dotslash_cache_temp_dir.path();
|
let dotslash_cache = dotslash_cache_temp_dir.path();
|
||||||
let transport = create_transport(codex_home.as_ref(), dotslash_cache).await?;
|
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.
|
// Create an MCP client that approves expected elicitation messages.
|
||||||
let project_root = TempDir::new()?;
|
let project_root = TempDir::new()?;
|
||||||
let project_root_path = project_root.path().canonicalize().unwrap();
|
let project_root_path = project_root.path().canonicalize().unwrap();
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ The Codex harness (used by the CLI and the VS Code extension) sends such request
|
|||||||
This package wraps the `codex-exec-mcp-server` binary and its helpers so that the shell MCP can be invoked via `npx -y @openai/codex-shell-tool-mcp`. It bundles:
|
This package wraps the `codex-exec-mcp-server` binary and its helpers so that the shell MCP can be invoked via `npx -y @openai/codex-shell-tool-mcp`. It bundles:
|
||||||
|
|
||||||
- `codex-exec-mcp-server` and `codex-execve-wrapper` built for macOS (arm64, x64) and Linux (musl arm64, musl x64).
|
- `codex-exec-mcp-server` and `codex-execve-wrapper` built for macOS (arm64, x64) and Linux (musl arm64, musl x64).
|
||||||
- A patched Bash that honors `BASH_EXEC_WRAPPER`, built for multiple glibc baselines (Ubuntu 24.04/22.04/20.04, Debian 12/11, CentOS-like 9) and macOS (15/14/13).
|
- A patched Bash that honors `EXEC_WRAPPER`, built for multiple glibc baselines (Ubuntu 24.04/22.04/20.04, Debian 12/11, CentOS-like 9) and macOS (15/14/13).
|
||||||
- A launcher (`bin/mcp-server.js`) that picks the correct binaries for the current `process.platform` / `process.arch`, specifying `--execve` and `--bash` for the MCP, as appropriate.
|
- A launcher (`bin/mcp-server.js`) that picks the correct binaries for the current `process.platform` / `process.arch`, specifying `--execve` and `--bash` for the MCP, as appropriate.
|
||||||
|
|
||||||
See [the README in the Codex repo](https://github.com/openai/codex/blob/main/codex-rs/exec-server/README.md) for details.
|
See [the README in the Codex repo](https://github.com/openai/codex/blob/main/codex-rs/exec-server/README.md) for details.
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ index 070f5119..d20ad2b9 100644
|
|||||||
char sample[HASH_BANG_BUFSIZ];
|
char sample[HASH_BANG_BUFSIZ];
|
||||||
size_t larray;
|
size_t larray;
|
||||||
|
|
||||||
+ char* exec_wrapper = getenv("BASH_EXEC_WRAPPER");
|
+ char* exec_wrapper = getenv("EXEC_WRAPPER");
|
||||||
+ if (exec_wrapper && *exec_wrapper && !whitespace (*exec_wrapper))
|
+ if (exec_wrapper && *exec_wrapper && !whitespace (*exec_wrapper))
|
||||||
+ {
|
+ {
|
||||||
+ char *orig_command = command;
|
+ char *orig_command = command;
|
||||||
|
|||||||
34
shell-tool-mcp/patches/zsh-exec-wrapper.patch
Normal file
34
shell-tool-mcp/patches/zsh-exec-wrapper.patch
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
diff --git a/Src/exec.c b/Src/exec.c
|
||||||
|
index 27bca11..baea760 100644
|
||||||
|
--- a/Src/exec.c
|
||||||
|
+++ b/Src/exec.c
|
||||||
|
@@ -507,7 +507,9 @@ zexecve(char *pth, char **argv, char **newenvp)
|
||||||
|
{
|
||||||
|
int eno;
|
||||||
|
static char buf[PATH_MAX * 2+1];
|
||||||
|
- char **eep;
|
||||||
|
+ char **eep, **exec_argv;
|
||||||
|
+ char *orig_pth = pth;
|
||||||
|
+ char *exec_wrapper;
|
||||||
|
|
||||||
|
unmetafy(pth, NULL);
|
||||||
|
for (eep = argv; *eep; eep++)
|
||||||
|
@@ -526,8 +528,17 @@ zexecve(char *pth, char **argv, char **newenvp)
|
||||||
|
|
||||||
|
if (newenvp == NULL)
|
||||||
|
newenvp = environ;
|
||||||
|
+ exec_argv = argv;
|
||||||
|
+ if ((exec_wrapper = getenv("EXEC_WRAPPER")) &&
|
||||||
|
+ *exec_wrapper && !inblank(*exec_wrapper)) {
|
||||||
|
+ exec_argv = argv - 2;
|
||||||
|
+ exec_argv[0] = exec_wrapper;
|
||||||
|
+ exec_argv[1] = orig_pth;
|
||||||
|
+ pth = exec_wrapper;
|
||||||
|
+ }
|
||||||
|
winch_unblock();
|
||||||
|
- execve(pth, argv, newenvp);
|
||||||
|
+ execve(pth, exec_argv, newenvp);
|
||||||
|
+ pth = orig_pth;
|
||||||
|
|
||||||
|
/* If the execve returns (which in general shouldn't happen), *
|
||||||
|
* then check for an errno equal to ENOEXEC. This errno is set *
|
||||||
Reference in New Issue
Block a user