{"record":{"id":"8c133797720faf02","repo":"gastownhall/beads","slug":"lock-busy-held-by-another-process","errorCode":null,"errorMessage":"lock busy: held by another process","messagePattern":"lock busy: held by another process","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"internal/lockfile/lock.go","lineNumber":12,"sourceCode":"package lockfile\n\nimport (\n\t\"errors\"\n)\n\n// ErrLocked is returned when a lock cannot be acquired because it is held by another process.\nvar ErrLocked = errProcessLocked\n\n// ErrLockBusy is returned when a non-blocking lock cannot be acquired\n// because another process holds a conflicting lock.\nvar ErrLockBusy = errors.New(\"lock busy: held by another process\")\n\n// IsLocked returns true if the error indicates a lock is held by another process.\nfunc IsLocked(err error) bool {\n\treturn errors.Is(err, errProcessLocked)\n}\n","sourceCodeStart":1,"sourceCodeEnd":18,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/lockfile/lock.go#L1-L18","documentation":"ErrLockBusy is the sentinel error returned when a non-blocking lock (sync lock, flock, or file lock) cannot be acquired because another process holds a conflicting lock. Callers should test with errors.Is(err, lockfile.ErrLockBusy) (or lockfile.IsLocked) and treat it as an expected contention outcome, not an unexpected failure.","triggerScenarios":"AcquireSyncLock, FlockSharedNonBlock, FlockExclusiveNonBlock, Acquire, or ExclusiveHolder called on a lock file while another process holds it in a conflicting mode; the functions are non-blocking so they return immediately with ErrLockBusy instead of waiting.","commonSituations":"Two bd daemons/CLI invocations running sync on the same workspace concurrently; a stale process still holding the lock while a new one starts; cron jobs overlapping; CI running two pipelines against one checkout.","solutions":["Use errors.Is(err, lockfile.ErrLockBusy) to detect contention and retry with backoff or skip gracefully","Wait for the other process to finish, or serialize work so only one process locks the workspace at a time","Investigate for a stale lock holder (check the holder reported by ExclusiveHolder) and terminate it if it is orphaned"],"exampleFix":"// before\nerr := lockfile.AcquireSyncLock(path)\n// treat as fatal\n// after\nif err := lockfile.AcquireSyncLock(path); err != nil {\n    if errors.Is(err, lockfile.ErrLockBusy) {\n        return nil // another process is syncing; skip\n    }\n    return err\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":"func isLockBusy(err error) bool { return errors.Is(err, lockfile.ErrLockBusy) }","tryCatchPattern":"for attempt := 0; attempt < 5; attempt++ {\n    err := lockfile.AcquireSyncLock(path)\n    if err == nil { break }\n    if !errors.Is(err, lockfile.ErrLockBusy) { return err }\n    time.Sleep(backoff(attempt))\n}","preventionTips":["Always branch on errors.Is(err, lockfile.ErrLockBusy) before treating a lock error as fatal","Serialize scheduled jobs (cron flock or single-instance supervisor) to avoid predictable contention","Investigate persistent busy results — they usually indicate a stale or hung holder, not transient contention"],"tags":["locking","concurrency","file-lock","flock"],"backgroundTag":"lock-contention","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}