gastownhall/beads · warning
lock already held by another process
Error message
lock already held by another process
What it means
errProcessLocked is the Unix sentinel for a non-blocking flock that could not be acquired because another process holds the file locked (unix.Flock returned EWOULDBLOCK). It is the platform-specific underlying error; ErrLocked aliases it and IsLocked matches it, so callers detect contention portably with lockfile.IsLocked(err).
Source
Thrown at internal/lockfile/lock_unix.go:12
//go:build unix
package lockfile
import (
"errors"
"os"
"golang.org/x/sys/unix"
)
var errProcessLocked = errors.New("lock already held by another process")
// flockExclusive acquires an exclusive non-blocking lock on the file
func flockExclusive(f *os.File) error {
err := unix.Flock(int(f.Fd()), unix.LOCK_EX|unix.LOCK_NB)
if err == unix.EWOULDBLOCK {
return errProcessLocked
}
return err
}
// FlockExclusiveNonBlocking attempts to acquire an exclusive lock without blocking.
// Returns ErrLocked if the lock is held by another process.
func FlockExclusiveNonBlocking(f *os.File) error {
return flockExclusive(f)
}
// FlockExclusiveBlocking acquires an exclusive blocking lock on the file.
// This will wait until the lock is available.View on GitHub (pinned to 71377f2769)
Solutions
- Treat it as contention: retry with backoff or skip, via lockfile.IsLocked(err) or errors.Is(err, lockfile.ErrLockBusy)
- Find and stop the conflicting process holding the lock (ps/lsof on the lock file)
- Ensure long-running holders release locks promptly and avoid flocks on NFS shares
Example fix
// before
err := lockfile.Acquire(path)
if err != nil { log.Fatal(err) }
// after
if err := lockfile.Acquire(path); err != nil {
if lockfile.IsLocked(err) { return nil } // busy: another process holds it
return err
} Defensive patterns
Strategy: retry
Type guard
func isProcessLocked(err error) bool { return errors.Is(err, lockfile.ErrLockBusy) || lockfile.IsLocked(err) } Try / catch
err := lockfile.Acquire(path)
if lockfile.IsLocked(err) {
return retryAfterBackoff() // EWOULDBLOCK: another process holds the flock
}
if err != nil { return err } Prevention
- Use lockfile.IsLocked (not string matching) so Unix/Windows/WASM all behave identically
- Keep lock hold times short and release with defer
- Avoid flocks on NFS; prefer local filesystems or an app-level lock
When it happens
Trigger: flockExclusive (LOCK_EX|LOCK_NB) on a lock file while another process holds an exclusive (or conflicting shared) lock; surfaces through Acquire/FlockExclusiveNonBlock/AcquireSyncLock on Unix and macOS.
Common situations: Two bd processes on the same machine locking the same workspace lock file; an overlapping background sync; a hung process that never released its flock; NFS-mounted directories where flock semantics vary.
Related errors
- lock busy: held by another process
- timeout waiting for cache lock on %s
- lock already held by another process
- ErrBusy
- %w The Dolt database is locked.%s Try: bd doctor --fix (cle
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/184cd9baa383ce36.
Report an issue: GitHub.