Skip to content

Commit 39ae2ae

Browse files
milankinenclaude
andcommitted
Fall back to ~/.cache/airlock/sock for long sandbox paths
AF_UNIX sun_path caps at 104 bytes on macOS (108 on Linux). Deeply nested project paths overflow that, so `airlock start` fails to bind cli.sock at `<sandbox_dir>/cli.sock` with EINVAL and `airlock exec` can never find it. Add crate::cache::cli_sock_path which returns the in-sandbox path when it fits, else a stable short fallback at `~/.cache/airlock/sock/<sha256-prefix-of-sandbox_dir>.sock`. Both server (cmd_start) and client (cmd_exec) compute the same path from the sandbox dir, so no pointer file is needed. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 63b7955 commit 39ae2ae

3 files changed

Lines changed: 43 additions & 10 deletions

File tree

app/airlock-cli/src/cache.rs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
//! Per-sandbox state (CA, disk image, overlay, etc.) lives in
66
//! `<project>/.airlock/sandbox/` — see `sandbox.rs`.
77
8-
use std::path::PathBuf;
8+
use std::path::{Path, PathBuf};
9+
10+
use sha2::{Digest, Sha256};
911

1012
/// On-disk format version for the per-layer cache. Bumped whenever the
1113
/// on-disk contract changes (e.g. the extractor now sets
@@ -50,6 +52,33 @@ pub fn cache_dir() -> anyhow::Result<PathBuf> {
5052
Ok(dir)
5153
}
5254

55+
/// Where the CLI RPC Unix socket lives for the sandbox at `sandbox_dir`.
56+
///
57+
/// Default is `<sandbox_dir>/cli.sock`. `AF_UNIX` has a hard 104-byte
58+
/// `sun_path` limit on macOS (108 on Linux); deeply nested project
59+
/// paths overflow that, so we fall back to
60+
/// `~/.cache/airlock/sock/<hash>.sock` — a short, stable location
61+
/// derived from the sandbox dir so `airlock start` and `airlock exec`
62+
/// both compute the same path without any pointer file.
63+
///
64+
/// The parent directory is created on demand.
65+
pub fn cli_sock_path(sandbox_dir: &Path) -> anyhow::Result<PathBuf> {
66+
// 103 = min(sun_path) across linux/macos, minus trailing NUL.
67+
const SUN_PATH_SAFE: usize = 103;
68+
69+
let default = sandbox_dir.join(airlock_common::CLI_SOCK_FILENAME);
70+
if default.as_os_str().len() <= SUN_PATH_SAFE {
71+
return Ok(default);
72+
}
73+
74+
let mut hasher = Sha256::new();
75+
hasher.update(sandbox_dir.as_os_str().as_encoded_bytes());
76+
let hash = hex::encode(&hasher.finalize()[..8]);
77+
let dir = cache_dir()?.join("sock");
78+
std::fs::create_dir_all(&dir)?;
79+
Ok(dir.join(format!("{hash}.sock")))
80+
}
81+
5382
/// Root of the OCI cache (`~/.cache/airlock/oci/`), created if absent.
5483
/// Holds the `images/` and `layers/` subtrees — kept under a dedicated
5584
/// namespace so other cache kinds (VM assets, …) don't collide.

app/airlock-cli/src/cli/cmd_exec.rs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -160,16 +160,20 @@ pub async fn main(args: ExecArgs) -> anyhow::Result<i32> {
160160
Ok(exit_code)
161161
}
162162

163-
/// Walk up from `start` looking for `.airlock/sandbox/<CLI_SOCK_FILENAME>`.
164-
/// Returns the first match, or `None` if nothing is found before the
165-
/// filesystem root.
163+
/// Walk up from `start` looking for `.airlock/sandbox/`. For each
164+
/// match resolve the CLI sock path — which is either that directory's
165+
/// `cli.sock` or a hash-keyed fallback under `~/.cache/airlock/sock/`
166+
/// when the in-sandbox path would exceed the `AF_UNIX` limit. Returns
167+
/// the first resolved path that exists on disk.
166168
fn find_cli_sock(start: &std::path::Path) -> Option<PathBuf> {
167169
for dir in start.ancestors() {
168-
let candidate = dir
169-
.join(".airlock")
170-
.join("sandbox")
171-
.join(airlock_common::CLI_SOCK_FILENAME);
172-
if candidate.exists() {
170+
let sandbox_dir = dir.join(".airlock").join("sandbox");
171+
if !sandbox_dir.is_dir() {
172+
continue;
173+
}
174+
if let Ok(candidate) = crate::cache::cli_sock_path(&sandbox_dir)
175+
&& candidate.exists()
176+
{
173177
return Some(candidate);
174178
}
175179
}

app/airlock-cli/src/cli/cmd_start.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ async fn run(
226226
// The server needs a copy of the sandbox's resolved env so it can layer
227227
// `airlock exec -e KEY=VAL` overrides on top without the exec client
228228
// having to re-resolve the project.
229-
let sock_path = project.sandbox_dir.join(airlock_common::CLI_SOCK_FILENAME);
229+
let sock_path = crate::cache::cli_sock_path(&project.sandbox_dir)?;
230230
let base_env = vm.env.clone();
231231
tokio::task::spawn_local(cli_server::serve(sock_path, supervisor.clone(), base_env));
232232

0 commit comments

Comments
 (0)