{"record":{"id":"62627e393fef377b","repo":"gastownhall/beads","slug":"workspacegate-lock-s-w","errorCode":null,"errorMessage":"workspacegate: lock %s: %w","messagePattern":"workspacegate: lock (.+?): %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/workspacegate/gate.go","lineNumber":412,"sourceCode":"\n\ttry := lockfile.FlockSharedNonBlock\n\tif mode == Exclusive {\n\t\ttry = lockfile.FlockExclusiveNonBlock\n\t}\n\n\tnotified := false\n\tfor {\n\t\terr := try(f)\n\t\tif err == nil {\n\t\t\th := &Handle{gate: g, mode: mode, f: f}\n\t\t\tif mode == Exclusive {\n\t\t\t\tg.writeInfo(opts.Reason)\n\t\t\t}\n\t\t\treturn h, nil\n\t\t}\n\t\tif !errors.Is(err, lockfile.ErrLockBusy) && !lockfile.IsLocked(err) {\n\t\t\t_ = f.Close()\n\t\t\treturn nil, fmt.Errorf(\"workspacegate: lock %s: %w\", g.path, err)\n\t\t}\n\t\tif !notified {\n\t\t\tnotified = true\n\t\t\tif opts.OnWait != nil {\n\t\t\t\topts.OnWait(g.busyDetail(mode))\n\t\t\t}\n\t\t}\n\t\tremaining := time.Until(deadline)\n\t\tif opts.Wait <= 0 || remaining <= 0 {\n\t\t\t_ = f.Close()\n\t\t\treturn nil, fmt.Errorf(\"workspacegate: %s (%s mode) held by %s: %w\",\n\t\t\t\tg.path, mode, g.busyDetail(mode), ErrBusy)\n\t\t}\n\t\t// Never sleep past the wait budget: a Wait shorter than the poll\n\t\t// interval must still come back within (about) Wait, and the\n\t\t// deadline is re-checked above before any further attempt.\n\t\tsleep := poll\n\t\tif remaining < sleep {","sourceCodeStart":394,"sourceCodeEnd":430,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/workspacegate/gate.go#L394-L430","documentation":"Acquire attempted a non-blocking flock (shared or exclusive) on the gate file and got an error that is neither lockfile.ErrLockBusy nor an IsLocked-style busy signal. Only genuine flock failures (bad fd, signal/interrupt, OS-level errors) reach this wrap; busy contention is handled separately by the retry loop. The file descriptor is closed before returning.","triggerScenarios":"Calling Acquire when lockfile.FlockSharedNonBlock or FlockExclusiveNonBlock returns a hard error other than busy, e.g. EINTR repeatedly, an invalid/closed file handle, or an OS that does not support flock on the filesystem (some network filesystems).","commonSituations":"Gate file on NFS or other filesystems where flock is unsupported or unreliable; fd exhaustion (EMFILE) preventing a usable handle; unusual kernel/security-policy interference (seccomp denying flock).","solutions":["Inspect the wrapped error with errors.As(*os.SyscallError)/errno to identify the exact flock failure.","Move the gate file onto a local filesystem (e.g. /tmp or the repo's .git dir) instead of NFS/network storage.","Check for fd exhaustion (ulimit -n) if the error is EMFILE/ENOLCK.","Retry the Acquire if the wrapped errno is transient (EINTR/ENOLCK under heavy load)."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"h, err := g.Acquire(ctx, mode, opts)\nif err != nil {\n    var se *os.SyscallError\n    if errors.As(err, &se) && isTransientFlockErrno(se.Err) {\n        // transient (EINTR/ENOLCK): back off and retry\n        time.Sleep(100 * time.Millisecond)\n        h, err = g.Acquire(ctx, mode, opts)\n    }\n    if err != nil {\n        return err\n    }\n}\ndefer h.Close()","preventionTips":["Avoid placing gate files on NFS/network filesystems that lack advisory flock support.","Keep fd limits reasonable and close handles promptly.","Log the wrapped errno to distinguish permanent from transient flock failures."],"tags":["locking","flock","filesystem","workspacegate"],"backgroundTag":"flock-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}