Hmbown/CodeWhale · error
failed to inherit reviewed plugin executable descriptor
Error message
failed to inherit reviewed plugin executable descriptor
What it means
On Unix, the reviewed-plugin launcher keeps the verified executable's file descriptor open across execve by clearing FD_CLOEXEC via fcntl(F_SETFD), then spawns through /proc/self/fd/N (or /dev/fd/N). This error means fcntl(F_GETFD) or fcntl(F_SETFD) returned a negative result on that descriptor (crates/tui/src/mcp.rs:924-927), so descriptor-based launch cannot be guaranteed and the spawn aborts before any exec.
Source
Thrown at crates/tui/src/mcp.rs:926
.iter()
.map(|byte| format!("{byte:02x}"))
.collect::<String>();
if &actual != expected {
anyhow::bail!("reviewed plugin executable bytes changed before spawn");
}
file.seek(std::io::SeekFrom::Start(0))
.context("rewind reviewed launch file after verification")?;
#[cfg(unix)]
let launch_path = {
use std::os::fd::AsRawFd as _;
let fd = file.as_raw_fd();
// SAFETY: `fd` is owned by `file`; clearing only FD_CLOEXEC keeps
// that same descriptor available across the imminent exec.
let flags = unsafe { libc::fcntl(fd, libc::F_GETFD) };
if flags < 0 || unsafe { libc::fcntl(fd, libc::F_SETFD, flags & !libc::FD_CLOEXEC) } < 0
{
anyhow::bail!("failed to inherit reviewed plugin executable descriptor");
}
#[cfg(target_os = "linux")]
let prefix = "/proc/self/fd";
#[cfg(not(target_os = "linux"))]
let prefix = "/dev/fd";
std::ffi::OsString::from(format!("{prefix}/{fd}"))
};
#[cfg(not(unix))]
let launch_path = path.as_os_str().to_os_string();
self.opened_files.push(file);
Ok(launch_path)
}
fn bind_cwd(&mut self, cwd: &Path) -> Result<()> {
#[cfg(unix)]
{View on GitHub (pinned to 8880682c63)
Solutions
- Retry the connection once - a fresh open yields a fresh descriptor and normally succeeds.
- Check fd pressure: ulimit -n and the count in /proc/<pid>/fd; raise the limit if near exhaustion.
- Audit embedding code for loops that close foreign descriptors (pre-exec hygiene like `for fd in 3..N { close(fd) }`) and exclude this one.
- If it reproduces under a sandbox/seccomp profile, permit fcntl(F_GETFD/F_SETFD) or report a bug with an strace.
Example fix
// before: single attempt; a rare EBADF aborts plugin spawn
let conn = McpConnection::connect_with_policy(name, cfg, &timeouts, policy).await?;
// after: retry once - descriptor races are transient
let conn = match McpConnection::connect_with_policy(name, cfg.clone(), &timeouts, policy).await {
Ok(conn) => conn,
Err(err) if err.to_string().contains("failed to inherit reviewed plugin") => {
McpConnection::connect_with_policy(name, cfg, &timeouts, policy).await?
}
Err(err) => return Err(err),
}; Defensive patterns
Strategy: try-catch
Try / catch
let conn = match McpConnection::connect_with_policy(name, cfg.clone(), &timeouts, policy).await {
Ok(conn) => conn,
Err(err) if err.to_string().contains("failed to inherit reviewed plugin") => {
// Transient descriptor race: one retry on a freshly opened file.
McpConnection::connect_with_policy(name, cfg, &timeouts, policy).await?
}
Err(err) => return Err(err),
}; Prevention
- Raise RLIMIT_NOFILE (ulimit -n) when running many MCP connections.
- Never close file descriptors you do not own in pre-exec or embedder code.
- If running under seccomp/sandbox profiles, allow fcntl(F_GETFD/F_SETFD).
When it happens
Trigger: fcntl failing with EBADF because the descriptor was closed concurrently by another thread/task, fd-table pressure, or a seccomp/sandbox profile denying fcntl. Cannot fire in normal operation.
Common situations: Embedders or test harnesses that aggressively close inherited fds before spawn; restrictive containers (gVisor, seccomp filters); fd-limit exhaustion with many concurrent MCP connections.
Related errors
- reviewed plugin stage could not be opened for launch
- reviewed plugin stdio cwd escaped its staged root
- Refusing to {operation} MCP server '{server_name}' from plug
- Refusing MCP server '{server_name}': its remote endpoint no
- reviewed plugin executable bytes changed before spawn
AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16).
Data as JSON: /api/errors/c575d9b3e173d45d.
Report an issue: GitHub.