-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Description
Summary
When running turbo commands inside a sandboxed environment (like Claude Code's macOS sandbox), connecting to an externally-started daemon fails due to two independent issues. Both need to be addressed for full sandbox compatibility.
Context
Claude Code is Anthropic's AI coding assistant that runs bash commands in a macOS sandbox (using sandbox-exec / Seatbelt). The sandbox:
- Sets
TMPDIR=/tmp/claudefor write isolation - Blocks process signaling (
killsyscalls returnEPERM) - Allows Unix socket connections (configurable)
We want to connect to a turborepo daemon started outside the sandbox to benefit from caching and file watching capabilities.
Two Independent Issues
Issue 1: TMPDIR-Based Daemon Paths
Turborepo uses std::env::temp_dir() to locate daemon files. When TMPDIR differs between environments, the paths don't match:
- External daemon:
/var/folders/.../T/turbod/<hash>/ - Sandboxed turbo:
/tmp/claude/turbod/<hash>/
Current workaround: Start the daemon with matching TMPDIR:
TMPDIR=/tmp/claude turbo daemon startSuggested fix: Use a fixed, predictable path for daemon files (e.g., ~/.turbo/daemon/ or ~/.cache/turborepo/daemon/) instead of relying on TMPDIR. This would:
- Avoid TMPDIR mismatch issues entirely
- Make daemon location predictable across environments
- Match patterns used by other tools (Docker, npm, etc.)
Issue 2: Incorrect kill(pid, 0) EPERM Handling (Bug)
Even with matching paths, daemon detection fails. The pidlock verification in crates/turborepo-pidlock/src/lib.rs uses:
fn process_exists(pid: i32) -> bool {
unsafe {
let result = libc::kill(pid, 0);
result == 0
}
}Per POSIX, kill(pid, 0) returns:
0→ process exists AND caller can signal it-1withESRCH→ process does NOT exist-1withEPERM→ process EXISTS but caller lacks permission
The sandbox returns EPERM (blocks signaling), but turborepo interprets this as "process doesn't exist," leading to:
- Pidfile marked as "stale"
- Pidfile deleted
- Daemon reported as "not running"
- Attempt to start new daemon (fails due to other sandbox restrictions)
There is no workaround for this issue — it requires a code fix.
Suggested fix: Update process_exists() to correctly handle EPERM:
fn process_exists(pid: i32) -> bool {
unsafe {
let result = libc::kill(pid, 0);
if result == 0 {
return true;
}
// EPERM means process exists but we can't signal it
// ESRCH means process doesn't exist
*libc::__errno_location() != libc::ESRCH
}
}Alternatively, consider using socket connection as the primary liveness check instead of kill, since the socket is the actual communication channel anyway.
Reproduction
-
Start daemon externally:
TMPDIR=/tmp/claude turbo daemon start TMPDIR=/tmp/claude turbo daemon status # Shows running -
From sandboxed environment (or simulate with a restricted user):
turbo daemon status # WARNING: stale pid file at "/tmp/claude/turbod/.../turbod.pid" # daemon is not running
-
Verify daemon is actually alive:
# Socket connection works! node -e "require('net').connect('/tmp/claude/turbod/.../turbod.sock').on('connect', () => console.log('alive'))"
Environment
- Turbo version: 2.6.1
- OS: macOS 15.4 (Sequoia)
- Sandbox: Claude Code's Seatbelt-based sandbox
Related
- Claude Code sandboxing docs: https://docs.claude.com/en/docs/claude-code/sandboxing
- Sandbox runtime (open source): https://github.com/anthropic-experimental/sandbox-runtime