[Bug]: Stale data directory lock causes permanent DataDirectoryLocked crash loop on macOS due to lack of PID reuse verification
Issue Origin
Observed or reproduced in a real environment
Bug Description
In openviking/utils/process_lock.py, _is_pid_alive(pid) is used by acquire_data_dir_lock to check if another OpenViking instance holds the advisory data directory lock (.openviking.pid).
While Linux has explicit handling to check /proc/{pid}/cmdline for PID recycling (to prevent false positives, ref #1088), macOS (darwin) lacks any command line or process identity verification. On macOS, _is_pid_alive falls back to os.kill(pid, 0).
If the machine reboots, shuts down abruptly, or the process terminates unexpectedly without running exit hooks, .openviking.pid remains on disk. If the OS subsequently reassigns that recycled PID to an unrelated long-lived process (such as a system daemon, driver extension like /System/Library/DriverExtensions/IOUserBluetoothSerial, or another app), os.kill(pid, 0) succeeds or encounters PermissionError (which is caught and passed), and _is_pid_alive unconditionally returns True.
This causes OpenViking to falsely believe another instance is active, raising DataDirectoryLocked and crashing immediately upon launch. When configured as a persistent daemon or LaunchAgent, this results in an endless restart/crash loop.
Steps to Reproduce
- Start
openviking-serveron macOS (which creates~/.openviking/data/.openviking.pid). - Simulate a crash / reboot by leaving
.openviking.pidintact whileopenviking-serveris stopped. - Find an unrelated active PID on macOS (e.g. any long-running system daemon like
launchd,WindowServer, or driver extension) and write that PID into.openviking.pid. - Run
openviking-server. - Observe immediate crash with
DataDirectoryLocked.
Expected Behavior
_is_pid_alive should verify the process command line on macOS (e.g. using ps -p <pid> -o command=). If the live process does not match openviking or openviking-server, the lock should be treated as stale, logged, and safely overwritten, matching the behavior on Linux.
Actual Behavior
openviking-server raises DataDirectoryLocked and exits immediately, preventing startup until the stale .openviking.pid is manually deleted.
Error Logs
openviking.utils.process_lock.DataDirectoryLocked: Another OpenViking process (PID 860) is already using the data directory '~/.openviking/data'. Running multiple OpenViking instances on the same data directory causes silent storage contention and data corruption.
To fix this, use one of these approaches:
1. Use HTTP mode: start a single openviking-server and connect via --transport http (recommended for multi-session hosts)
2. Use separate data directories for each instance
3. Stop the other process (PID 860) first
2026-09-17 19:24:26,358 - uvicorn.error - ERROR - Application startup failed. Exiting.OpenViking Version
0.4.13 (and reproduced against main branch commit)
Python Version
3.13.2
Operating System
macOS
Model Backend
Other
Additional Context
Suggested fix in openviking/utils/process_lock.py (_is_pid_alive):
if sys.platform.startswith("linux"):
try:
with open(f"/proc/{pid}/cmdline", "rb") as f:
cmdline = f.read().decode("utf-8", errors="replace").lower()
if "openviking" not in cmdline and "openviking-server" not in cmdline:
logger.info(
"PID %d is alive but not an OpenViking process (cmdline: %.100s). "
"Assuming stale lock from recycled PID.",
pid,
cmdline[:100],
)
return False
except OSError:
# /proc not available or process exited between kill and open
pass
elif sys.platform == "darwin":
try:
import subprocess
res = subprocess.run(
["ps", "-p", str(pid), "-o", "command="],
capture_output=True,
text=True,
check=False,
)
cmdline = res.stdout.strip().lower()
if cmdline and "openviking" not in cmdline and "ov" not in cmdline:
logger.info(
"PID %d is alive but not an OpenViking process (cmdline: %.100s). "
"Assuming stale lock from recycled PID.",
pid,
cmdline[:100],
)
return False
except OSError:
passSource: volcengine/OpenViking