Skip to content

Commit 42ac131

Browse files
wan9chicodex
andcommitted
fix(ipc): use named FIFOs on Unix
Codex CLI and Claude Code default sandboxes block Unix-domain sockets, so cached task execution fails before runner-aware tools or fspy can communicate with vp run. Replace Unix-domain sockets on Unix with a private FIFO transport and move the complete cross-platform transport behind the new vite_ipc server/client API. Callers now exchange only an opaque generated name and byte streams; FIFO and Windows named-pipe details stay inside the transport crate. The sandbox snapshots preserve the next behavior: Claude runtime completes fspy tracking, while Codex reaches the separately tracked fspy shared-memory restriction. Closes #562. Refs #561 and #563. Co-authored-by: GPT-5 Codex <codex@openai.com>
1 parent c86913c commit 42ac131

16 files changed

Lines changed: 801 additions & 179 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Changelog
22

3+
- **Fixed** `vp run` no longer fails while setting up task communication in default Codex CLI and Claude Code sandboxes that block Unix domain sockets ([#562](https://github.com/voidzero-dev/vite-task/issues/562), [#569](https://github.com/voidzero-dev/vite-task/pull/569)).
34
- **Fixed** Automatic file-access tracking now works inside coding-agent sandboxes, including the default Codex CLI and Claude Code sandboxes ([#563](https://github.com/voidzero-dev/vite-task/issues/563), [#576](https://github.com/voidzero-dev/vite-task/pull/576)).
45
- **Added** Tasks now run with `VP_RUN=1` set, so tools can tell they are running under `vp run` instead of being invoked directly ([#570](https://github.com/voidzero-dev/vite-task/pull/570)).
56
- **Fixed** The task cache now supports much larger automatically tracked input sets without hitting wincode's default 4 MiB sequence preallocation limit ([#554](https://github.com/voidzero-dev/vite-task/pull/554)).

Cargo.lock

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

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ uuid = "1.18.1"
149149
vec1 = "1.12.1"
150150
vite_glob = { path = "crates/vite_glob" }
151151
vite_graph_ser = { path = "crates/vite_graph_ser" }
152+
pipe_socket = { path = "crates/pipe_socket" }
152153
vite_path = { path = "crates/vite_path" }
153154
vite_powershell = { path = "crates/vite_powershell" }
154155
vite_select = { path = "crates/vite_select" }

crates/pipe_socket/Cargo.toml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
[package]
2+
name = "pipe_socket"
3+
version = "0.0.0"
4+
edition.workspace = true
5+
license.workspace = true
6+
publish = false
7+
rust-version.workspace = true
8+
9+
[dependencies]
10+
tokio = { workspace = true, features = ["io-util", "net"] }
11+
12+
[target.'cfg(unix)'.dependencies]
13+
nix = { workspace = true, features = ["fs", "poll"] }
14+
tempfile = { workspace = true }
15+
tokio = { workspace = true, features = ["fs"] }
16+
uuid = { workspace = true, features = ["v4"] }
17+
vite_path = { workspace = true }
18+
19+
[target.'cfg(windows)'.dependencies]
20+
uuid = { workspace = true, features = ["v4"] }
21+
winapi = { workspace = true, features = ["namedpipeapi"] }
22+
23+
[dev-dependencies]
24+
tokio = { workspace = true, features = ["macros", "rt", "time"] }
25+
26+
[lints]
27+
workspace = true
28+
29+
[lib]
30+
doctest = false
31+
test = false

crates/pipe_socket/README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# `pipe_socket`
2+
3+
Socket-style server-client IPC, implemented on named pipes rather than Unix
4+
domain sockets.
5+
6+
A server binds under an opaque name and hands that name to its clients, for
7+
example through an environment variable. Clients connect synchronously and get
8+
a byte stream; the server accepts each connection asynchronously with Tokio.
9+
The API is four items: `Server::bind`, `Server::name`, `Server::accept`, and
10+
`Client::connect`.
11+
12+
The transport is named pipes on every platform: FIFOs on Unix, named pipe
13+
objects on Windows. Both are available in environments that block Unix domain
14+
sockets, such as coding-agent sandboxes.
15+
16+
A client whose server is gone gets an error, not a hang, on both platforms.

crates/pipe_socket/src/lib.rs

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
#![doc = include_str!("../README.md")]
2+
3+
use std::{
4+
ffi::OsStr,
5+
io::{self, Read, Write},
6+
pin::Pin,
7+
task::{Context, Poll},
8+
};
9+
10+
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
11+
12+
#[cfg(unix)]
13+
mod unix;
14+
#[cfg(unix)]
15+
use unix as imp;
16+
#[cfg(windows)]
17+
mod windows;
18+
#[cfg(windows)]
19+
use windows as imp;
20+
21+
#[cfg(not(any(unix, windows)))]
22+
compile_error!("pipe_socket supports only Unix and Windows");
23+
24+
/// A named server that asynchronously accepts byte-stream connections.
25+
pub struct Server {
26+
inner: imp::Server,
27+
}
28+
29+
impl Server {
30+
/// Creates a server with a new unique name.
31+
///
32+
/// # Errors
33+
///
34+
/// Returns an error if the platform transport cannot be created.
35+
pub fn bind() -> io::Result<Self> {
36+
imp::Server::bind().map(|inner| Self { inner })
37+
}
38+
39+
/// Returns the opaque name clients use to connect to this server.
40+
#[must_use]
41+
pub fn name(&self) -> &OsStr {
42+
self.inner.name()
43+
}
44+
45+
/// Waits for and accepts the next client connection.
46+
///
47+
/// # Errors
48+
///
49+
/// Returns an error if the connection cannot be accepted.
50+
pub async fn accept(&mut self) -> io::Result<ServerConnection> {
51+
self.inner.accept().await.map(|inner| ServerConnection { inner })
52+
}
53+
}
54+
55+
/// The server side of an accepted byte-stream connection.
56+
pub struct ServerConnection {
57+
inner: imp::ServerConnection,
58+
}
59+
60+
impl AsyncRead for ServerConnection {
61+
fn poll_read(
62+
mut self: Pin<&mut Self>,
63+
cx: &mut Context<'_>,
64+
buf: &mut ReadBuf<'_>,
65+
) -> Poll<io::Result<()>> {
66+
Pin::new(&mut self.inner).poll_read(cx, buf)
67+
}
68+
}
69+
70+
impl AsyncWrite for ServerConnection {
71+
fn poll_write(
72+
mut self: Pin<&mut Self>,
73+
cx: &mut Context<'_>,
74+
buf: &[u8],
75+
) -> Poll<io::Result<usize>> {
76+
Pin::new(&mut self.inner).poll_write(cx, buf)
77+
}
78+
79+
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
80+
Pin::new(&mut self.inner).poll_flush(cx)
81+
}
82+
83+
fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
84+
Pin::new(&mut self.inner).poll_shutdown(cx)
85+
}
86+
}
87+
88+
/// A synchronous client byte stream connected by a server name.
89+
pub struct Client {
90+
inner: imp::Client,
91+
}
92+
93+
impl Client {
94+
/// Connects to the server identified by `name`.
95+
///
96+
/// # Errors
97+
///
98+
/// Returns an error if the name is invalid or the server cannot be reached.
99+
pub fn connect(name: &OsStr) -> io::Result<Self> {
100+
imp::Client::connect(name).map(|inner| Self { inner })
101+
}
102+
}
103+
104+
impl Read for Client {
105+
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
106+
self.inner.read(buf)
107+
}
108+
}
109+
110+
impl Write for Client {
111+
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
112+
self.inner.write(buf)
113+
}
114+
115+
fn flush(&mut self) -> io::Result<()> {
116+
self.inner.flush()
117+
}
118+
}

0 commit comments

Comments
 (0)