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

  1. Treat it as contention: retry with backoff or skip, via lockfile.IsLocked(err) or errors.Is(err, lockfile.ErrLockBusy)
  2. Find and stop the conflicting process holding the lock (ps/lsof on the lock file)
  3. 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

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


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/184cd9baa383ce36. Report an issue: GitHub.