{"record":{"id":"8ea0780bb3b1b9ba","repo":"gastownhall/beads","slug":"acquiring-lock-non-blocking-w","errorCode":null,"errorMessage":"acquiring lock (non-blocking): %w","messagePattern":"acquiring lock \\(non-blocking\\): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/linear/synclock.go","lineNumber":60,"sourceCode":"\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)\n\t}\n\n\treturn &SyncLock{infoPath: infoPath, file: f, metadata: metadata}, nil\n}\n\n// Release releases the sync lock. The kernel-lock file is NOT removed — doing\n// so after unlocking creates a race where a blocked waiter acquires the old\n// inode while a new process creates a fresh file at the same path, splitting\n// lock identity. On Unix, inline owner metadata is truncated while still\n// holding the lock. On Windows, a separate advisory record is cleared while","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/linear/synclock.go#L42-L78","documentation":"AcquireSyncLock with wait=false attempts a non-blocking exclusive flock; a contention result is converted to SyncLockHeldError, but any other flock failure is wrapped as this error. It means the non-blocking acquire failed for a reason other than the lock simply being held.","triggerScenarios":"Calling AcquireSyncLock(beadsDir, false) when lockfile.FlockExclusiveNonBlocking returns an error that is neither lockfile.IsLocked(err) nor ErrLockBusy — e.g. I/O errors, EBADF, or platform flock anomalies.","commonSituations":"Corrupted or unreadable lock file; filesystem not supporting flock; running on a platform where the lock helper hits an unexpected OS error; unusual permission changes on .linear-sync.lock after creation.","solutions":["Inspect the wrapped error to find the underlying OS failure and fix it (permissions, filesystem support)","Delete a stale/corrupt .linear-sync.lock file (only if no sync is running) and retry","Move the beads directory to a local filesystem if flock is unsupported","If the intent was to skip when another sync runs, note that contention is reported as SyncLockHeldError instead — handle that type separately"],"exampleFix":"// before: treating all errors the same\nlock, err := linear.AcquireSyncLock(beadsDir, false)\nif err != nil { return err }\n// after: distinguish contention from real failure\nlock, err := linear.AcquireSyncLock(beadsDir, false)\nvar held *linear.SyncLockHeldError\nif errors.As(err, &held) {\n    return nil // another sync running; skip\n}\nif err != nil { return fmt.Errorf(\"lock failure: %w\", err) }","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"func isSyncLockHeld(err error) bool {\n    var held *linear.SyncLockHeldError\n    return errors.As(err, &held)\n}\n// Usage: if !isSyncLockHeld(err) { return err } // real failure, not contention","tryCatchPattern":"lock, err := linear.AcquireSyncLock(beadsDir, false)\nif err != nil {\n    if isSyncLockHeld(err) {\n        return nil // expected contention; skip run\n    }\n    return fmt.Errorf(\"non-blocking lock acquire failed: %w\", err)\n}\ndefer lock.Release()","preventionTips":["Always distinguish SyncLockHeldError (expected contention) from generic errors using errors.As","Don't chmod or delete .linear-sync.lock while syncs may run","Run on filesystems with reliable flock support","Log the wrapped cause for unexpected non-contention failures"],"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"}