Skip to content

Commit b54a7c5

Browse files
committed
fix: add test that verifies that codex-exec-mcp-server starts up
1 parent e91bb6b commit b54a7c5

File tree

16 files changed

+550
-33
lines changed

16 files changed

+550
-33
lines changed

.github/workflows/rust-ci.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,20 @@ jobs:
369369

370370
steps:
371371
- uses: actions/checkout@v6
372+
373+
# Some integration tests rely on DotSlash being installed.
374+
# See https://github.com/openai/codex/pull/7617.
375+
- name: Install DotSlash
376+
uses: facebook/install-dotslash@v2
377+
378+
- name: Pre-fetch DotSlash artifacts
379+
# The Bash wrapper is not available on Windows.
380+
if: ${{ !startsWith(matrix.runner, 'windows') }}
381+
shell: bash
382+
run: |
383+
set -euo pipefail
384+
dotslash -- fetch exec-server/tests/suite/bash
385+
372386
- uses: dtolnay/rust-toolchain@1.90
373387
with:
374388
targets: ${{ matrix.target }}

codex-rs/Cargo.lock

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

codex-rs/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ codex-utils-readiness = { path = "utils/readiness" }
9696
codex-utils-string = { path = "utils/string" }
9797
codex-windows-sandbox = { path = "windows-sandbox-rs" }
9898
core_test_support = { path = "core/tests/common" }
99+
exec_server_test_support = { path = "exec-server/tests/common" }
99100
mcp-types = { path = "mcp-types" }
100101
mcp_test_support = { path = "mcp-server/tests/common" }
101102

@@ -178,8 +179,8 @@ seccompiler = "0.5.0"
178179
sentry = "0.34.0"
179180
serde = "1"
180181
serde_json = "1"
181-
serde_yaml = "0.9"
182182
serde_with = "3.16"
183+
serde_yaml = "0.9"
183184
serial_test = "3.2.0"
184185
sha1 = "0.10.6"
185186
sha2 = "0.10"

codex-rs/exec-server/Cargo.toml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
[package]
2-
name = "codex-exec-server"
3-
version.workspace = true
42
edition.workspace = true
53
license.workspace = true
4+
name = "codex-exec-server"
5+
version.workspace = true
66

77
[[bin]]
88
name = "codex-execve-wrapper"
@@ -56,5 +56,9 @@ tracing = { workspace = true }
5656
tracing-subscriber = { workspace = true, features = ["env-filter", "fmt"] }
5757

5858
[dev-dependencies]
59+
assert_cmd = { workspace = true }
60+
exec_server_test_support = { workspace = true }
61+
maplit = { workspace = true }
5962
pretty_assertions = { workspace = true }
6063
tempfile = { workspace = true }
64+
which = { workspace = true }

codex-rs/exec-server/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,6 @@ pub use posix::main_execve_wrapper;
66

77
#[cfg(unix)]
88
pub use posix::main_mcp_server;
9+
10+
#[cfg(unix)]
11+
pub use posix::ExecResult;

codex-rs/exec-server/src/posix.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ mod mcp_escalation_policy;
8282
mod socket;
8383
mod stopwatch;
8484

85+
pub use mcp::ExecResult;
86+
8587
/// Default value of --execve option relative to the current executable.
8688
/// Note this must match the name of the binary as specified in Cargo.toml.
8789
const CODEX_EXECVE_WRAPPER_EXE_NAME: &str = "codex-execve-wrapper";

codex-rs/exec-server/src/posix/escalate_client.rs

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::collections::HashMap;
12
use std::io;
23
use std::os::fd::AsRawFd;
34
use std::os::fd::FromRawFd as _;
@@ -34,21 +35,30 @@ pub(crate) async fn run(file: String, argv: Vec<String>) -> anyhow::Result<i32>
3435
.send_with_fds(&HANDSHAKE_MESSAGE, &[server.into_inner().into()])
3536
.await
3637
.context("failed to send handshake datagram")?;
37-
let env = std::env::vars()
38-
.filter(|(k, _)| {
39-
!matches!(
40-
k.as_str(),
41-
ESCALATE_SOCKET_ENV_VAR | BASH_EXEC_WRAPPER_ENV_VAR
42-
)
43-
})
44-
.collect();
38+
let mut env = HashMap::new();
39+
const MAX_ENV_ENTRY_LEN: i64 = 8_192;
40+
for (key, value) in std::env::vars() {
41+
if matches!(
42+
key.as_str(),
43+
ESCALATE_SOCKET_ENV_VAR | BASH_EXEC_WRAPPER_ENV_VAR
44+
) {
45+
continue;
46+
}
47+
let entry_len = (key.len() + value.len()) as i64;
48+
if entry_len > MAX_ENV_ENTRY_LEN {
49+
tracing::debug!(key, entry_len, "skipping oversized environment variable");
50+
continue;
51+
}
52+
env.insert(key, value);
53+
}
54+
let request = EscalateRequest {
55+
file: file.clone().into(),
56+
argv: argv.clone(),
57+
workdir: std::env::current_dir()?,
58+
env,
59+
};
4560
client
46-
.send(EscalateRequest {
47-
file: file.clone().into(),
48-
argv: argv.clone(),
49-
workdir: std::env::current_dir()?,
50-
env,
51-
})
61+
.send(request)
5262
.await
5363
.context("failed to send EscalateRequest")?;
5464
let message = client.receive::<EscalateResponse>().await?;

codex-rs/exec-server/src/posix/mcp.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ pub struct ExecParams {
5454
pub login: Option<bool>,
5555
}
5656

57-
#[derive(Debug, serde::Serialize, schemars::JsonSchema)]
57+
#[derive(Debug, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
5858
pub struct ExecResult {
5959
pub exit_code: i32,
6060
pub output: String,

codex-rs/exec-server/src/posix/socket.rs

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -182,21 +182,26 @@ fn send_message_bytes(socket: &Socket, data: &[u8], fds: &[OwnedFd]) -> std::io:
182182
frame.extend_from_slice(&encode_length(data.len())?);
183183
frame.extend_from_slice(data);
184184

185-
let mut control = vec![0u8; control_space_for_fds(fds.len())];
186-
unsafe {
187-
let cmsg = control.as_mut_ptr().cast::<libc::cmsghdr>();
188-
(*cmsg).cmsg_len = libc::CMSG_LEN(size_of::<RawFd>() as c_uint * fds.len() as c_uint) as _;
189-
(*cmsg).cmsg_level = libc::SOL_SOCKET;
190-
(*cmsg).cmsg_type = libc::SCM_RIGHTS;
191-
let data_ptr = libc::CMSG_DATA(cmsg).cast::<RawFd>();
192-
for (i, fd) in fds.iter().enumerate() {
193-
data_ptr.add(i).write(fd.as_raw_fd());
194-
}
195-
}
196-
185+
let mut control;
197186
let payload = [IoSlice::new(&frame)];
198-
let msg = MsgHdr::new().with_buffers(&payload).with_control(&control);
199-
let mut sent = socket.sendmsg(&msg, 0)?;
187+
let mut sent = if fds.is_empty() {
188+
socket.send(&frame)?
189+
} else {
190+
control = vec![0u8; control_space_for_fds(fds.len())];
191+
unsafe {
192+
let cmsg = control.as_mut_ptr().cast::<libc::cmsghdr>();
193+
(*cmsg).cmsg_len =
194+
libc::CMSG_LEN(size_of::<RawFd>() as c_uint * fds.len() as c_uint) as _;
195+
(*cmsg).cmsg_level = libc::SOL_SOCKET;
196+
(*cmsg).cmsg_type = libc::SCM_RIGHTS;
197+
let data_ptr = libc::CMSG_DATA(cmsg).cast::<RawFd>();
198+
for (i, fd) in fds.iter().enumerate() {
199+
data_ptr.add(i).write(fd.as_raw_fd());
200+
}
201+
}
202+
let msg = MsgHdr::new().with_buffers(&payload).with_control(&control);
203+
socket.sendmsg(&msg, 0)?
204+
};
200205
while sent < frame.len() {
201206
let bytes = socket.send(&frame[sent..])?;
202207
if bytes == 0 {
@@ -236,8 +241,9 @@ fn send_datagram_bytes(socket: &Socket, data: &[u8], fds: &[OwnedFd]) -> std::io
236241
format!("too many fds: {}", fds.len()),
237242
));
238243
}
239-
let mut control = vec![0u8; control_space_for_fds(fds.len())];
244+
let mut control = Vec::new();
240245
if !fds.is_empty() {
246+
control = vec![0u8; control_space_for_fds(fds.len())];
241247
unsafe {
242248
let cmsg = control.as_mut_ptr().cast::<libc::cmsghdr>();
243249
(*cmsg).cmsg_len =

codex-rs/exec-server/tests/all.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// Single integration test binary that aggregates all test modules.
2+
// The submodules live in `tests/suite/`.
3+
mod suite;

0 commit comments

Comments
 (0)