{"record":{"id":"a4c1b6ee8a58c7b1","repo":"zeroclaw-labs/zeroclaw","slug":"local-ipc-endpoint-lock-was-unlinked-while-bein","errorCode":null,"errorMessage":"local IPC endpoint lock {} was unlinked while being opened","messagePattern":"local IPC endpoint lock (.+?) was unlinked while being opened","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-runtime/src/rpc/local.rs","lineNumber":350,"sourceCode":"        let euid = unsafe { libc::geteuid() };\n        if metadata.uid() != euid {\n            anyhow::bail!(\n                \"local IPC endpoint lock {} is owned by uid {}, not the \\\n                 daemon user; remove it or choose a different socket path\",\n                lock_path.display(),\n                metadata.uid()\n            );\n        }\n        if metadata.mode() & 0o077 != 0 {\n            anyhow::bail!(\n                \"local IPC endpoint lock {} is accessible to other users \\\n                 (mode {:o}); restrict it to 0600 or remove it\",\n                lock_path.display(),\n                metadata.mode() & 0o7777\n            );\n        }\n        if metadata.nlink() == 0 {\n            anyhow::bail!(\n                \"local IPC endpoint lock {} was unlinked while being opened\",\n                lock_path.display()\n            );\n        }\n        Ok(())\n    }\n\n    impl EndpointLock {\n        pub(super) fn acquire(path: &Path) -> Result<Self> {\n            let mut lock_name = path.as_os_str().to_os_string();\n            lock_name.push(\".lock\");\n            let lock_path = PathBuf::from(lock_name);\n            require_trusted_lock_dir(&lock_path)?;\n            let file = OpenOptions::new()\n                .read(true)\n                .write(true)\n                .create(true)\n                .mode(0o600)","sourceCodeStart":332,"sourceCodeEnd":368,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-runtime/src/rpc/local.rs#L332-L368","documentation":"After opening the lock file, its link count (nlink) was 0 — the file was unlinked between open and fstat. With no directory entry, the dev/inode identity check cannot guarantee that two daemons see the same lock inode, so ZeroClaw refuses to proceed. This is a transient race, typically caused by cleanup tooling deleting the persistent lock (the lock is intentionally never unlinked by ZeroClaw itself).","triggerScenarios":"A tmpfiles.d/cron/systemd-tmpfiles rule deletes '*.lock' or the socket dir contents while the daemon starts; an operator manually rm'ed the lock at the same moment; NFS silly-rename semantics dropping the link; a container restart wiping a directory that is actually a bind-mounted file.","commonSituations":"systemd-tmpfiles cleans /run every boot and races a fast-starting daemon; aggressive /tmp cleaners; 'cleanup' scripts written before the lock was made persistent.","solutions":["Retry daemon startup once — the next attempt creates a fresh lock.","Find and stop whatever unlinks the lock: check systemd-tmpfiles rules, cron jobs, and operator habits.","Never delete <socket>.lock manually; it is persistent by design.","If on NFS, move the socket to a local filesystem."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"// transient unlink race: retry acquisition once before surfacing the error\nlet lock = match acquire_with_retry(&sock_path, 1) {\n    Ok(l) => l,\n    Err(e) if e.to_string().contains(\"was unlinked\") => { /* report cleanup-tool interference */ return Err(e) }\n    Err(e) => return Err(e),\n};","preventionTips":["Exclude the socket dir from tmpfiles.d/cron cleanup (or use Age= with sticky persistence).","Document that <socket>.lock is persistent by design and must never be deleted.","Avoid NFS for lock files; use a local filesystem."],"tags":["unix","ipc","lock-file","race-condition","filesystem","startup"],"backgroundTag":"file-race-condition","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}