facebook/flow · error · io::Error
lock file is already held
Error message
lock file is already held
What it means
The standalone Flow server enforces single-instance semantics at startup: acquire_lock creates the lock file (and its parent dirs) and takes an exclusive OS-level flock via try_lock. WouldBlock — another process holds the lock — maps to ErrorKind::AlreadyExists "lock file is already held". The holder's LockGuard removes the lock, socket, and pids files when it exits cleanly, so this error means a live (or leaked-fd) owner still exists.
Source
Thrown at rust_port/crates/flow_server/src/standalone.rs:842
match outcome {
RecheckOutcome::Ok => {}
}
}
}
fn acquire_lock(lock_path: &str) -> std::io::Result<std::fs::File> {
if let Some(parent) = Path::new(lock_path).parent() {
std::fs::create_dir_all(parent)?;
}
let file = std::fs::OpenOptions::new()
.write(true)
.create(true)
.truncate(false)
.open(lock_path)?;
match file.try_lock() {
Ok(()) => Ok(file),
Err(std::fs::TryLockError::WouldBlock) => Err(std::io::Error::new(
std::io::ErrorKind::AlreadyExists,
"lock file is already held",
)),
Err(err) => Err(err.into()),
}
}
struct LockGuard {
lock_path: String,
socket_path: String,
pids_path: String,
}
impl Drop for LockGuard {
fn drop(&mut self) {
let _ = std::fs::remove_file(&self.pids_path);
let _ = std::fs::remove_file(&self.socket_path);
let _ = std::fs::remove_file(&self.lock_path);View on GitHub (pinned to f88ac94bcf)
Solutions
- Find the running instance for that root (process list for the standalone binary, or the pids file next to the lock) and stop it through its normal stop path, then start again.
- If no live process holds it, delete the stale lock file and restart.
- If you genuinely want a second server, give it a different root/lock path.
Example fix
# before: second start while the first server is alive flow standalone ... # -> lock file is already held # after: stop or reuse the existing instance first pgrep -af 'flow.*standalone' # find the live holder kill <pid> # or use the provided stop command rm -f /path/to/flow.lock # only if no process holds it flow standalone ...
Defensive patterns
Strategy: validation
Validate before calling
use std::fs::OpenOptions;
// Before starting a server, check whether another live instance owns the lock.
fn lock_is_held(path: &str) -> bool {
OpenOptions::new().write(true).create(true).open(path)
.map(|f| f.try_lock().is_err()) // fs4::FileExt on std's File
.unwrap_or(false)
} Type guard
fn is_lock_held(e: &std::io::Error) -> bool {
e.kind() == std::io::ErrorKind::AlreadyExists && e.to_string().contains("already held")
} Try / catch
On AlreadyExists 'lock file is already held', do not retry in a loop: look up the running instance (pids file next to the lock, process list) and either connect to it or stop it, then start. Only remove the lock file manually after confirming no process holds it.
Prevention
- Always stop servers through their stop path so LockGuard cleans lock/socket/pids files.
- In test harnesses, use unique per-run lock paths to avoid collisions.
- After crashes, sweep stale lock/socket files before restarting.
When it happens
Trigger: Starting a second standalone server with the same lock path while the first is alive; a previous server left running in the background (editor integration, earlier terminal); an orphaned process whose fd still holds the flock after its parent session died.
Common situations: Forgetting a server started earlier; a test harness leaving servers behind; a hung server that never released the lock; flock semantics keeping the lock alive while any duplicated fd exists.
Related errors
AI-assisted analysis of facebook/flow@f88ac94bcf (2026-08-20).
Data as JSON: /api/errors/0542a336476bc2cd.
Report an issue: GitHub.