gastownhall/beads · warning
lock already held by another process
Error message
lock already held by another process
What it means
errProcessLocked is the Windows sentinel returned when a non-blocking exclusive LockFileEx (LOCKFILE_EXCLUSIVE_LOCK|LOCKFILE_FAIL_IMMEDIATELY) cannot acquire the lock because another process holds it. Like the Unix variant, it is matched portably through lockfile.IsLocked / errors.Is(err, lockfile.ErrLockBusy).
Source
Thrown at internal/lockfile/lock_windows.go:13
//go:build windows
package lockfile
import (
"errors"
"os"
"syscall"
"golang.org/x/sys/windows"
)
var errProcessLocked = errors.New("lock already held by another process")
// flockExclusive acquires an exclusive non-blocking lock on the file using LockFileEx
func flockExclusive(f *os.File) error {
// LOCKFILE_EXCLUSIVE_LOCK (2) | LOCKFILE_FAIL_IMMEDIATELY (1) = 3
const flags = windows.LOCKFILE_EXCLUSIVE_LOCK | windows.LOCKFILE_FAIL_IMMEDIATELY
// Create overlapped structure for the entire file
ol := &windows.Overlapped{}
// Lock entire file (0xFFFFFFFF, 0xFFFFFFFF = maximum range)
err := windows.LockFileEx(
windows.Handle(f.Fd()),
flags,
0, // reserved
0xFFFFFFFF, // number of bytes to lock (low)
0xFFFFFFFF, // number of bytes to lock (high)
ol,
)View on GitHub (pinned to 71377f2769)
Solutions
- Handle it as expected contention via lockfile.IsLocked(err): retry with backoff or skip
- Identify the holder (Resource Monitor / handle.exe on the lock file) and close or kill the stale process
- Ensure holders release locks deterministically (defer unlock) and avoid long-lived exclusive holds
Example fix
// before
err := lockfile.Acquire(path)
if err != nil { return err } // fatal on busy
// after
if err := lockfile.Acquire(path); err != nil {
if lockfile.IsLocked(err) { return nil } // busy: retry later or skip
return err
} Defensive patterns
Strategy: retry
Type guard
func isWindowsLockBusy(err error) bool { return lockfile.IsLocked(err) } Try / catch
err := lockfile.Acquire(path)
if lockfile.IsLocked(err) {
return retryWithBackoff() // LockFileEx failed immediately: another holder
}
if err != nil { return err } Prevention
- Match with lockfile.IsLocked so the same code path handles Unix and Windows
- Release locks with defer to avoid orphaned handles after panics
- Check for stale holders with handle.exe/Resource Monitor when busy persists across restarts
When it happens
Trigger: flockExclusive on a Windows lock file while another process holds the byte-range lock; surfaces through Acquire/FlockExclusiveNonBlock/AcquireSyncLock on Windows builds when contention occurs.
Common situations: Two bd processes on the same Windows machine targeting one workspace; an antivirus/editor or previous crashed-run descendant keeping a handle open; overlapping scheduled tasks.
Related errors
- lock busy: held by another process
- lock already held by another process
- lock already held by another process
- ErrLockHeld
- ErrBusy
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/cf7f92aa37f24184.
Report an issue: GitHub.