zeroclaw-labs/zeroclaw · error
local IPC endpoint lock {} was replaced while being acquired
Error message
local IPC endpoint lock {} was replaced while being acquired; refusing to share lifecycle ownership What it means
The dev/inode identity of `<socket>.lock` changed between opening the file and taking the flock: something replaced the directory entry mid-acquisition. Accepting the replacement would let a second daemon lock a different inode and both believe they own socket lifecycle cleanup, so ZeroClaw bails instead of sharing ownership.
Source
Thrown at crates/zeroclaw-runtime/src/rpc/local.rs:398
lock_path.display()
)
})?;
require_trusted_lock_file(&metadata, &lock_path)?;
match file.try_lock() {
Ok(()) => {
// The directory entry must still name the locked inode.
// A swap between open and lock would let a second daemon
// lock the replacement and re-enter the split-ownership
// race this lock exists to prevent.
let current = SocketIdentity::read(&lock_path).with_context(|| {
format!(
"confirming local IPC endpoint lifecycle lock {}",
lock_path.display()
)
})?;
if current != SocketIdentity::from_metadata(&metadata) {
anyhow::bail!(
"local IPC endpoint lock {} was replaced while being \
acquired; refusing to share lifecycle ownership",
lock_path.display()
);
}
Ok(Self { _file: file })
}
Err(TryLockError::WouldBlock) => Err(std::io::Error::new(
ErrorKind::AddrInUse,
format!(
"local IPC endpoint lifecycle is already owned at {}",
path.display()
),
)
.into()),
Err(TryLockError::Error(error)) => Err(error).with_context(|| {
format!(
"locking local IPC endpoint lifecycle at {}",View on GitHub (pinned to 88bb9c8533)
Solutions
- Retry acquisition once — a fresh, stable lock usually resolves it.
- Stop whatever replaces the lock: remove rm/recreate logic for the socket dir from unit files and scripts (create-if-missing only).
- Ensure only one daemon start path exists (no overlapping systemd units/containers on the same ZEROCLAW_SOCKET).
- Point ZEROCLAW_SOCKET at a private directory no other tooling manages.
Defensive patterns
Strategy: validation
Validate before calling
// Detect active interference before start: if the lock identity keeps changing
// between two stats, something is replacing entries — investigate instead of racing it.
fn lock_identity_stable(sock: &std::path::Path) -> Option<(u64, u64)> {
let mut s = sock.as_os_str().to_os_string();
s.push(".lock");
let p = std::path::PathBuf::from(s);
let a = std::fs::symlink_metadata(&p).ok()?;
std::thread::sleep(std::time::Duration::from_millis(50));
let b = std::fs::symlink_metadata(&p).ok()?;
(a.dev() == b.dev() && a.ino() == b.ino()).then_some((a.dev(), a.ino()))
} Try / catch
Err(e) if e.to_string().contains("was replaced while being acquired") => {
// something swaps the lock: stop scripts that rm/recreate the socket dir, then retry
} Prevention
- Service units must create the socket dir if missing, never rm+recreate it.
- Serialize daemon restarts (systemd dependency or a supervisor) so two starts cannot race.
- Keep external tooling away from the daemon's private runtime dir.
When it happens
Trigger: A cleanup process unlinks and recreates the lock exactly while the daemon starts; two daemons racing to start with one creating a fresh lock over the other's; configuration management atomically replacing files in the runtime dir (mv into place).
Common situations: Restart loops where a stopping daemon's cleanup recreates state while the next one starts; scripts that 'reset' the socket directory by rm+mkdir on every service start; hostile co-tenant in a shared directory swapping the lock.
Related errors
- local IPC endpoint lock {} was unlinked while being opened
- local IPC endpoint lock {} is not a regular file; remove it
- local IPC endpoint lock {} is owned by uid {}, not the daemo
- local IPC endpoint lock {} is accessible to other users (mod
- local IPC endpoint lock directory {} is owned by uid {}; it
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/fad22d73976bf547.
Report an issue: GitHub.