{"record":{"id":"cf7f92aa37f24184","repo":"gastownhall/beads","slug":"lock-already-held-by-another-process-cf7f92","errorCode":null,"errorMessage":"lock already held by another process","messagePattern":"lock already held by another process","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"internal/lockfile/lock_windows.go","lineNumber":13,"sourceCode":"//go:build windows\n\npackage lockfile\n\nimport (\n\t\"errors\"\n\t\"os\"\n\t\"syscall\"\n\n\t\"golang.org/x/sys/windows\"\n)\n\nvar errProcessLocked = errors.New(\"lock already held by another process\")\n\n// flockExclusive acquires an exclusive non-blocking lock on the file using LockFileEx\nfunc flockExclusive(f *os.File) error {\n\t// LOCKFILE_EXCLUSIVE_LOCK (2) | LOCKFILE_FAIL_IMMEDIATELY (1) = 3\n\tconst flags = windows.LOCKFILE_EXCLUSIVE_LOCK | windows.LOCKFILE_FAIL_IMMEDIATELY\n\n\t// Create overlapped structure for the entire file\n\tol := &windows.Overlapped{}\n\n\t// Lock entire file (0xFFFFFFFF, 0xFFFFFFFF = maximum range)\n\terr := windows.LockFileEx(\n\t\twindows.Handle(f.Fd()),\n\t\tflags,\n\t\t0,          // reserved\n\t\t0xFFFFFFFF, // number of bytes to lock (low)\n\t\t0xFFFFFFFF, // number of bytes to lock (high)\n\t\tol,\n\t)","sourceCodeStart":1,"sourceCodeEnd":31,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/lockfile/lock_windows.go#L1-L31","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nerr := lockfile.Acquire(path)\nif err != nil { return err } // fatal on busy\n// after\nif err := lockfile.Acquire(path); err != nil {\n    if lockfile.IsLocked(err) { return nil } // busy: retry later or skip\n    return err\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":"func isWindowsLockBusy(err error) bool { return lockfile.IsLocked(err) }","tryCatchPattern":"err := lockfile.Acquire(path)\nif lockfile.IsLocked(err) {\n    return retryWithBackoff() // LockFileEx failed immediately: another holder\n}\nif err != nil { return err }","preventionTips":["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"],"tags":["locking","windows","file-lock","concurrency"],"backgroundTag":"lock-contention","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}