gastownhall/beads · error
util: opening lock file: %w
Error message
util: opening lock file: %w
What it means
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.
Source
Thrown at internal/storage/dbproxy/util/flock.go:33
}
// 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
}
// Unlock releases the flock and closes the underlying file. Panics on
// failure to prevent silent deadlocks.View on GitHub (pinned to 71377f2769)
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
Example fix
// before
f, _ := os.Stat(lockPath) // blindly TryLock
lock, err := util.TryLock(lockPath)
// after
if info, serr := os.Stat(lockPath); serr == nil && info.IsDir() {
return fmt.Errorf("lock path %s is a directory", lockPath)
}
lock, err := util.TryLock(lockPath) Defensive patterns
Strategy: validation
Validate before calling
if st, err := os.Stat(lockPath); err == nil {
if st.IsDir() { return fmt.Errorf("%s is a directory", lockPath) }
if err := syscall.Access(lockPath, syscall.W_OK); err != nil {
return fmt.Errorf("lock file %s not writable (owner %d): %w", lockPath, st.Sys().(*syscall.Stat_t).Uid, err)
}
} Try / catch
lock, err := util.TryLock(lockPath)
if err != nil {
if errors.Is(err, fs.ErrPermission) {
os.Remove(lockPath) // stale lock from another user; verify no live holder first
lock, err = util.TryLock(lockPath)
}
if err != nil { return err }
}
defer lock.Close() Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- util: creating lock directory: %w
- dolt path is not executable
- failed to create backup directory: %w
- failed to create temp file: %w
- failed to create backup file: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/b2eb6d6488a9f39d.
Report an issue: GitHub.