{"record":{"id":"b2eb6d6488a9f39d","repo":"gastownhall/beads","slug":"util-opening-lock-file-w","errorCode":null,"errorMessage":"util: opening lock file: %w","messagePattern":"util: opening lock file: %w","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/dbproxy/util/flock.go","lineNumber":33,"sourceCode":"}\n\n// Lock holds an exclusive flock on a file.\ntype Lock struct {\n\tf *os.File\n}\n\n// TryLock attempts to acquire a non-blocking exclusive flock on lockPath.\n// The parent directory is created (mode 0700) if it does not exist. On\n// contention returns an error that satisfies lockfile.IsLocked, so callers\n// can detect \"another holder is alive\" and produce their own contextual\n// error message.\nfunc TryLock(lockPath string) (*Lock, error) {\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0700); err != nil {\n\t\treturn nil, fmt.Errorf(\"util: creating lock directory: %w\", err)\n\t}\n\tf, err := os.OpenFile(lockPath, os.O_CREATE|os.O_RDWR, 0600) //nolint:gosec // lockPath comes from caller-derived dirs, not user input\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"util: opening lock file: %w\", err)\n\t}\n\tif err := lockfile.FlockExclusiveNonBlocking(f); err != nil {\n\t\t_ = f.Close()\n\t\treturn nil, err\n\t}\n\treturn &Lock{f: f}, nil\n}\n\n// File returns the underlying *os.File. Useful for fork+exec lock-fd\n// inheritance: pass it via cmd.ExtraFiles, then Close() the parent's fd —\n// the child retains the lock through its inherited fd, which references the\n// same open file description.\nfunc (l *Lock) File() *os.File {\n\treturn l.f\n}\n\n// Unlock releases the flock and closes the underlying file. Panics on\n// failure to prevent silent deadlocks.","sourceCodeStart":15,"sourceCodeEnd":51,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/dbproxy/util/flock.go#L15-L51","documentation":"TryLock opens (or creates) the lock file with O_CREATE|O_RDWR mode 0600 before attempting an exclusive flock; this error wraps the os.OpenFile failure. The lock was never taken because the file itself could not be opened.","triggerScenarios":"TryLock called with a lockPath whose final component cannot be opened for read/write — the path exists as a directory, permission denied on an existing lock file owned by another user, or immutable/read-only filesystem.","commonSituations":"A previous run as root left a 0600 lock file now unwritable by the app user; the lockPath names an existing directory; stale lock file left in a read-only-mounted volume after a crash.","solutions":["If the lock file exists but is owned by another user, chown it or remove it (confirm no live holder first via lsof/fuser)","Check the path is a file, not a directory: rm the directory or point TryLock at a different path","Fix file permissions so the current user has read/write (0600) on the lock file","Ensure the volume is mounted read-write; on immutable storage, relocate the lock path"],"exampleFix":"// before\nf, _ := os.Stat(lockPath) // blindly TryLock\nlock, err := util.TryLock(lockPath)\n// after\nif info, serr := os.Stat(lockPath); serr == nil && info.IsDir() {\n    return fmt.Errorf(\"lock path %s is a directory\", lockPath)\n}\nlock, err := util.TryLock(lockPath)","handlingStrategy":"validation","validationCode":"if st, err := os.Stat(lockPath); err == nil {\n    if st.IsDir() { return fmt.Errorf(\"%s is a directory\", lockPath) }\n    if err := syscall.Access(lockPath, syscall.W_OK); err != nil {\n        return fmt.Errorf(\"lock file %s not writable (owner %d): %w\", lockPath, st.Sys().(*syscall.Stat_t).Uid, err)\n    }\n}","typeGuard":null,"tryCatchPattern":"lock, err := util.TryLock(lockPath)\nif err != nil {\n    if errors.Is(err, fs.ErrPermission) {\n        os.Remove(lockPath) // stale lock from another user; verify no live holder first\n        lock, err = util.TryLock(lockPath)\n    }\n    if err != nil { return err }\n}\ndefer lock.Close()","preventionTips":["Run the application as one stable user so 0600 lock files stay writable","Clean up stale lock files during maintenance windows (after checking no live holder via lsof)","Never point lockPath at a directory or a path inside a read-only mount"],"tags":["go","filesystem","locking","permissions","openfile"],"backgroundTag":"lock-file-open-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}