gastownhall/beads · error

util: creating lock directory: %w

Error message

util: creating lock directory: %w

What it means

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.

Source

Thrown at internal/storage/dbproxy/util/flock.go:29

// Unlocker is the interface for releasing an acquired lock.
type Unlocker interface {
	Unlock()
}

// Lock holds an exclusive flock on a file.
type Lock struct {
	f *os.File
}

// TryLock attempts to acquire a non-blocking exclusive flock on lockPath.
// The parent directory is created (mode 0700) if it does not exist. On
// contention returns an error that satisfies lockfile.IsLocked, so callers
// can detect "another holder is alive" and produce their own contextual
// error message.
func TryLock(lockPath string) (*Lock, error) {
	if err := os.MkdirAll(filepath.Dir(lockPath), 0700); err != nil {
		return nil, fmt.Errorf("util: creating lock directory: %w", err)
	}
	f, err := os.OpenFile(lockPath, os.O_CREATE|os.O_RDWR, 0600) //nolint:gosec // lockPath comes from caller-derived dirs, not user input
	if err != nil {
		return nil, fmt.Errorf("util: opening lock file: %w", err)
	}
	if err := lockfile.FlockExclusiveNonBlocking(f); err != nil {
		_ = f.Close()
		return nil, err
	}
	return &Lock{f: f}, nil
}

// File returns the underlying *os.File. Useful for fork+exec lock-fd
// inheritance: pass it via cmd.ExtraFiles, then Close() the parent's fd —
// the child retains the lock through its inherited fd, which references the
// same open file description.
func (l *Lock) File() *os.File {
	return l.f

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check whether a component of the lock path is an existing file: ls -la each ancestor directory and remove/rename the offending file
  2. Fix permissions: chown/chmod the ancestor directories (0700 is required to be creatable by the current user)
  3. Ensure the filesystem is writable (mount rw, not read-only container root)
  4. Validate the lockPath configuration value points inside your data directory

Example fix

// before
lock, err := util.TryLock("/var/lib/beads/db.lock") // /var/lib is root-owned
// after
os.MkdirAll("/var/lib/beads", 0o700) // or choose a dir the current user owns
lock, err := util.TryLock("/var/lib/beads/db.lock")
Defensive patterns

Strategy: validation

Validate before calling

dir := filepath.Dir(lockPath)
if st, err := os.Stat(dir); err == nil && !st.IsDir() {
    return fmt.Errorf("%s exists and is not a directory", dir)
}
if err := syscall.Access(filepath.Dir(dir), syscall.W_OK); err != nil {
    return fmt.Errorf("cannot create %s: %w", dir, err)
}

Try / catch

lock, err := util.TryLock(lockPath)
if err != nil {
    if errors.Is(err, fs.ErrPermission) {
        return fmt.Errorf("lock dir %s not writable by %s: %w", filepath.Dir(lockPath), os.Getuid(), err)
    }
    return err
}
defer lock.Close()

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/f0f8bb826237e66c. Report an issue: GitHub.