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

  1. 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.
  2. If no live process holds it, delete the stale lock file and restart.
  3. 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

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.