{"record":{"id":"cec0dca269389132","repo":"gastownhall/beads","slug":"opening-lock-file-w","errorCode":null,"errorMessage":"opening lock file: %w","messagePattern":"opening lock file: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/linear/synclock.go","lineNumber":44,"sourceCode":"type SyncLockInfo struct {\n\tPID     int\n\tStarted time.Time\n}\n\n// AcquireSyncLock acquires the sync lock for the given beads directory.\n// 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}","sourceCodeStart":26,"sourceCodeEnd":62,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/linear/synclock.go#L26-L62","documentation":"AcquireSyncLock opens (creating if needed) <beadsDir>/sync.lock with os.OpenFile(O_CREATE|O_RDWR, 0600) to hold the flock. If opening fails it wraps the OS error as \"opening lock file: %w\" and returns nil. This happens before any locking attempt, so it reflects file-creation problems rather than contention.","triggerScenarios":"os.OpenFile on the lock path fails because the beads directory is not writable, the lock file exists with restrictive ownership/permissions, the path is on a read-only filesystem, or too many file descriptors are open.","commonSituations":"Another user previously created sync.lock with different ownership; running in a container with a read-only .beads mount; ulimit -n exhausted after leaked file handles; antivirus/backup software locking the file on some platforms.","solutions":["Fix ownership/permissions on <beadsDir> and sync.lock so the running user can create/read-write (0600)","Check file descriptor usage (ulimit -n, lsof) for leaked handles if the error is EMFILE","Verify the filesystem is writable and not read-only mounted","If a stale lock file from another user blocks you, remove it after confirming no sync process is running"],"exampleFix":"// before\nlock, err := AcquireSyncLock(beadsDir, true)\n// after: check writability first\nif err := checkWritable(beadsDir); err != nil {\n    return fmt.Errorf(\"cannot acquire sync lock: %w\", err)\n}\nlock, err := AcquireSyncLock(beadsDir, true)","handlingStrategy":"validation","validationCode":"// ensure directory is writable before acquiring\nprobe := filepath.Join(beadsDir, \".probe\")\nif err := os.WriteFile(probe, nil, 0600); err != nil {\n    return fmt.Errorf(\"beads dir not writable: %w\", err)\n}\nos.Remove(probe)\nlock, err := AcquireSyncLock(beadsDir, true)","typeGuard":"null","tryCatchPattern":"lock, err := AcquireSyncLock(beadsDir, true)\nif err != nil && strings.Contains(err.Error(), \"opening lock file\") {\n    return fmt.Errorf(\"cannot open sync lock in %s: %w\", beadsDir, err)\n}","preventionTips":["Keep sync.lock owned by the running user with 0600 permissions","Check ulimit -n when running long-lived processes that open many files","Verify the filesystem is writable before starting sync operations","Remove stale lock files only after confirming no live sync process holds them"],"tags":["filesystem","permissions","locking","file-descriptors"],"backgroundTag":"lock-file-open-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}