gastownhall/beads · error

acquiring lock (blocking): %w

Error message

acquiring lock (blocking): %w

What it means

AcquireSyncLock serializes concurrent `bd linear sync` runs per beads directory using an flock on `.linear-sync.lock`. When wait=true, it blocks until the lock is free; this error wraps a failure from that blocking acquisition. The lock file was opened successfully, but the blocking flock call itself failed (rather than merely being contended).

Source

Thrown at internal/linear/synclock.go:50

// If wait is true, blocks until the lock is available. If false, returns
// an error immediately when the lock is held by another live process.
func AcquireSyncLock(beadsDir string, wait bool) (*SyncLock, error) {
	lockPath := filepath.Join(beadsDir, syncLockFilename)
	infoPath := syncLockMetadataPath(beadsDir, lockPath)

	if err := os.MkdirAll(beadsDir, 0755); err != nil {
		return nil, fmt.Errorf("creating beads directory: %w", err)
	}

	f, err := os.OpenFile(lockPath, os.O_CREATE|os.O_RDWR, 0600) // #nosec G304 -- lockPath is constrained to the beads directory.
	if err != nil {
		return nil, fmt.Errorf("opening lock file: %w", err)
	}

	if wait {
		if err := lockfile.FlockExclusiveBlocking(f); err != nil {
			_ = f.Close()
			return nil, fmt.Errorf("acquiring lock (blocking): %w", err)
		}
	} else {
		if err := lockfile.FlockExclusiveNonBlocking(f); err != nil {
			if lockfile.IsLocked(err) || err == lockfile.ErrLockBusy {
				info := readContendedSyncLockInfo(infoPath)
				_ = f.Close()
				return nil, &SyncLockHeldError{Info: info}
			}
			_ = f.Close()
			return nil, fmt.Errorf("acquiring lock (non-blocking): %w", err)
		}
	}

	metadata, err := publishSyncLockInfo(f, infoPath)
	if err != nil {
		_ = lockfile.FlockUnlock(f)
		_ = f.Close()
		return nil, fmt.Errorf("writing lock info: %w", err)

View on GitHub (pinned to 71377f2769)

Solutions

  1. Retry AcquireSyncLock once or twice — transient failures like EINTR or I/O hiccups often clear
  2. Check that the beads directory lives on a local filesystem that supports flock (not NFS without lock daemon)
  3. Inspect the wrapped error (%w) to identify the underlying errno and address it (permissions, disk space, etc.)
  4. If another sync appears stuck, check the PID in the lock metadata (.linear-sync.lock info) and kill or wait for that process

Example fix

// before: ignoring retryable failure
lock, err := linear.AcquireSyncLock(beadsDir, true)
if err != nil { return err }
// after: retry transient blocking-acquire failures
var lock *linear.SyncLock
var err error
for i := 0; i < 3; i++ {
    lock, err = linear.AcquireSyncLock(beadsDir, true)
    if err == nil { break }
    time.Sleep(100 * time.Millisecond)
}
if err != nil { return err }
Defensive patterns

Strategy: retry

Validate before calling

// Pre-check: is another sync likely running (advisory)?
if info := linear.IsProcessAliveFromLockInfo(beadsDir); info != nil && linear.IsProcessAlive(info.PID) {
    return fmt.Errorf("sync appears active (PID %d)", info.PID)
}

Try / catch

lock, err := linear.AcquireSyncLock(beadsDir, true)
if err != nil {
    var pathErr *fs.PathError
    if errors.As(err, &pathErr) {
        return fmt.Errorf("filesystem problem with %s: %w", pathErr.Path, err)
    }
    return fmt.Errorf("blocking lock acquire failed: %w", err)
}
defer lock.Release()

Prevention

When it happens

Trigger: Calling AcquireSyncLock(beadsDir, true) when lockfile.FlockExclusiveBlocking returns an error that is not a normal busy result — e.g. EINTR after repeated signal interruptions, I/O error on the lock file, or a platform-level flock failure.

Common situations: Process receiving many signals during a long wait on a heavily contended lock; NFS/network filesystems where flock is unreliable; disk-full or permission-degraded filesystems; Windows file-share violations from antivirus or backup tools holding the lock file.

Related errors


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