{"record":{"id":"f0f8bb826237e66c","repo":"gastownhall/beads","slug":"util-creating-lock-directory-w","errorCode":null,"errorMessage":"util: creating lock directory: %w","messagePattern":"util: creating lock directory: %w","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/dbproxy/util/flock.go","lineNumber":29,"sourceCode":"\n// Unlocker is the interface for releasing an acquired lock.\ntype Unlocker interface {\n\tUnlock()\n}\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","sourceCodeStart":11,"sourceCodeEnd":47,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/dbproxy/util/flock.go#L11-L47","documentation":"TryLock ensures the parent directory of the lock path exists (mode 0700) before creating the lock file; this error wraps an os.MkdirAll failure. It means the lock could not even be attempted because the containing directory could not be created.","triggerScenarios":"Calling util.TryLock(lockPath) where filepath.Dir(lockPath) cannot be created — a path component is a regular file, permission denied on an ancestor, or the path is on a read-only filesystem.","commonSituations":"Lock path misconfigured (e.g. .beads path colliding with an existing file); running under a different UID than the database directory owner; read-only root filesystem in containers; disk full preventing mkdir.","solutions":["Check whether a component of the lock path is an existing file: ls -la each ancestor directory and remove/rename the offending file","Fix permissions: chown/chmod the ancestor directories (0700 is required to be creatable by the current user)","Ensure the filesystem is writable (mount rw, not read-only container root)","Validate the lockPath configuration value points inside your data directory"],"exampleFix":"// before\nlock, err := util.TryLock(\"/var/lib/beads/db.lock\") // /var/lib is root-owned\n// after\nos.MkdirAll(\"/var/lib/beads\", 0o700) // or choose a dir the current user owns\nlock, err := util.TryLock(\"/var/lib/beads/db.lock\")","handlingStrategy":"validation","validationCode":"dir := filepath.Dir(lockPath)\nif st, err := os.Stat(dir); err == nil && !st.IsDir() {\n    return fmt.Errorf(\"%s exists and is not a directory\", dir)\n}\nif err := syscall.Access(filepath.Dir(dir), syscall.W_OK); err != nil {\n    return fmt.Errorf(\"cannot create %s: %w\", dir, err)\n}","typeGuard":null,"tryCatchPattern":"lock, err := util.TryLock(lockPath)\nif err != nil {\n    if errors.Is(err, fs.ErrPermission) {\n        return fmt.Errorf(\"lock dir %s not writable by %s: %w\", filepath.Dir(lockPath), os.Getuid(), err)\n    }\n    return err\n}\ndefer lock.Close()","preventionTips":["Derive lockPath from the same configurable data directory used by the database","Ensure the service runs as a consistent user across restarts (avoid root-created dirs)","Mount data volumes read-write; never place lock files on tmpfs that gets wiped"],"tags":["go","filesystem","locking","permissions"],"backgroundTag":"lock-directory-creation-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}