{"record":{"id":"ca5fed62ad5ebcbc","repo":"gastownhall/beads","slug":"acquiring-lock-blocking-w","errorCode":null,"errorMessage":"acquiring lock (blocking): %w","messagePattern":"acquiring lock \\(blocking\\): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/linear/synclock.go","lineNumber":50,"sourceCode":"// If wait is true, blocks until the lock is available. If false, returns\n// an error immediately when the lock is held by another live process.\nfunc AcquireSyncLock(beadsDir string, wait bool) (*SyncLock, error) {\n\tlockPath := filepath.Join(beadsDir, syncLockFilename)\n\tinfoPath := syncLockMetadataPath(beadsDir, lockPath)\n\n\tif err := os.MkdirAll(beadsDir, 0755); err != nil {\n\t\treturn nil, fmt.Errorf(\"creating beads directory: %w\", err)\n\t}\n\n\tf, err := os.OpenFile(lockPath, os.O_CREATE|os.O_RDWR, 0600) // #nosec G304 -- lockPath is constrained to the beads directory.\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"opening lock file: %w\", err)\n\t}\n\n\tif wait {\n\t\tif err := lockfile.FlockExclusiveBlocking(f); err != nil {\n\t\t\t_ = f.Close()\n\t\t\treturn nil, fmt.Errorf(\"acquiring lock (blocking): %w\", err)\n\t\t}\n\t} else {\n\t\tif err := lockfile.FlockExclusiveNonBlocking(f); err != nil {\n\t\t\tif lockfile.IsLocked(err) || err == lockfile.ErrLockBusy {\n\t\t\t\tinfo := readContendedSyncLockInfo(infoPath)\n\t\t\t\t_ = f.Close()\n\t\t\t\treturn nil, &SyncLockHeldError{Info: info}\n\t\t\t}\n\t\t\t_ = f.Close()\n\t\t\treturn nil, fmt.Errorf(\"acquiring lock (non-blocking): %w\", err)\n\t\t}\n\t}\n\n\tmetadata, err := publishSyncLockInfo(f, infoPath)\n\tif err != nil {\n\t\t_ = lockfile.FlockUnlock(f)\n\t\t_ = f.Close()\n\t\treturn nil, fmt.Errorf(\"writing lock info: %w\", err)","sourceCodeStart":32,"sourceCodeEnd":68,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/linear/synclock.go#L32-L68","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","solutions":["Retry AcquireSyncLock once or twice — transient failures like EINTR or I/O hiccups often clear","Check that the beads directory lives on a local filesystem that supports flock (not NFS without lock daemon)","Inspect the wrapped error (%w) to identify the underlying errno and address it (permissions, disk space, etc.)","If another sync appears stuck, check the PID in the lock metadata (.linear-sync.lock info) and kill or wait for that process"],"exampleFix":"// before: ignoring retryable failure\nlock, err := linear.AcquireSyncLock(beadsDir, true)\nif err != nil { return err }\n// after: retry transient blocking-acquire failures\nvar lock *linear.SyncLock\nvar err error\nfor i := 0; i < 3; i++ {\n    lock, err = linear.AcquireSyncLock(beadsDir, true)\n    if err == nil { break }\n    time.Sleep(100 * time.Millisecond)\n}\nif err != nil { return err }","handlingStrategy":"retry","validationCode":"// Pre-check: is another sync likely running (advisory)?\nif info := linear.IsProcessAliveFromLockInfo(beadsDir); info != nil && linear.IsProcessAlive(info.PID) {\n    return fmt.Errorf(\"sync appears active (PID %d)\", info.PID)\n}","typeGuard":null,"tryCatchPattern":"lock, err := linear.AcquireSyncLock(beadsDir, true)\nif err != nil {\n    var pathErr *fs.PathError\n    if errors.As(err, &pathErr) {\n        return fmt.Errorf(\"filesystem problem with %s: %w\", pathErr.Path, err)\n    }\n    return fmt.Errorf(\"blocking lock acquire failed: %w\", err)\n}\ndefer lock.Release()","preventionTips":["Keep the beads directory on a local filesystem that supports flock","Retry AcquireSyncLock a few times with small backoff for transient failures","Avoid stopping the process with signals while it waits on the lock; handle SIGINT gracefully","Monitor disk space and permissions on the beads directory"],"tags":["file-lock","concurrency","go","sync"],"backgroundTag":"file-lock-acquisition-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}