{"record":{"id":"184cd9baa383ce36","repo":"gastownhall/beads","slug":"lock-already-held-by-another-process","errorCode":null,"errorMessage":"lock already held by another process","messagePattern":"lock already held by another process","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"internal/lockfile/lock_unix.go","lineNumber":12,"sourceCode":"//go:build unix\n\npackage lockfile\n\nimport (\n\t\"errors\"\n\t\"os\"\n\n\t\"golang.org/x/sys/unix\"\n)\n\nvar errProcessLocked = errors.New(\"lock already held by another process\")\n\n// flockExclusive acquires an exclusive non-blocking lock on the file\nfunc flockExclusive(f *os.File) error {\n\terr := unix.Flock(int(f.Fd()), unix.LOCK_EX|unix.LOCK_NB)\n\tif err == unix.EWOULDBLOCK {\n\t\treturn errProcessLocked\n\t}\n\treturn err\n}\n\n// FlockExclusiveNonBlocking attempts to acquire an exclusive lock without blocking.\n// Returns ErrLocked if the lock is held by another process.\nfunc FlockExclusiveNonBlocking(f *os.File) error {\n\treturn flockExclusive(f)\n}\n\n// FlockExclusiveBlocking acquires an exclusive blocking lock on the file.\n// This will wait until the lock is available.","sourceCodeStart":1,"sourceCodeEnd":30,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/lockfile/lock_unix.go#L1-L30","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nerr := lockfile.Acquire(path)\nif err != nil { log.Fatal(err) }\n// after\nif err := lockfile.Acquire(path); err != nil {\n    if lockfile.IsLocked(err) { return nil } // busy: another process holds it\n    return err\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":"func isProcessLocked(err error) bool { return errors.Is(err, lockfile.ErrLockBusy) || lockfile.IsLocked(err) }","tryCatchPattern":"err := lockfile.Acquire(path)\nif lockfile.IsLocked(err) {\n    return retryAfterBackoff() // EWOULDBLOCK: another process holds the flock\n}\nif err != nil { return err }","preventionTips":["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"],"tags":["locking","unix","flock","concurrency"],"backgroundTag":"lock-contention","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}