gastownhall/beads · error
workspacegate: lock %s: %w
Error message
workspacegate: lock %s: %w
What it means
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.
Source
Thrown at internal/workspacegate/gate.go:412
try := lockfile.FlockSharedNonBlock
if mode == Exclusive {
try = lockfile.FlockExclusiveNonBlock
}
notified := false
for {
err := try(f)
if err == nil {
h := &Handle{gate: g, mode: mode, f: f}
if mode == Exclusive {
g.writeInfo(opts.Reason)
}
return h, nil
}
if !errors.Is(err, lockfile.ErrLockBusy) && !lockfile.IsLocked(err) {
_ = f.Close()
return nil, fmt.Errorf("workspacegate: lock %s: %w", g.path, err)
}
if !notified {
notified = true
if opts.OnWait != nil {
opts.OnWait(g.busyDetail(mode))
}
}
remaining := time.Until(deadline)
if opts.Wait <= 0 || remaining <= 0 {
_ = f.Close()
return nil, fmt.Errorf("workspacegate: %s (%s mode) held by %s: %w",
g.path, mode, g.busyDetail(mode), ErrBusy)
}
// Never sleep past the wait budget: a Wait shorter than the poll
// interval must still come back within (about) Wait, and the
// deadline is re-checked above before any further attempt.
sleep := poll
if remaining < sleep {View on GitHub (pinned to 71377f2769)
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).
Defensive patterns
Strategy: retry
Try / catch
h, err := g.Acquire(ctx, mode, opts)
if err != nil {
var se *os.SyscallError
if errors.As(err, &se) && isTransientFlockErrno(se.Err) {
// transient (EINTR/ENOLCK): back off and retry
time.Sleep(100 * time.Millisecond)
h, err = g.Acquire(ctx, mode, opts)
}
if err != nil {
return err
}
}
defer h.Close() Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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).
Related errors
- workspacegate: open gate %s: %w
- workspacegate: probe %s: %w
- lock busy: held by another process
- lock already held by another process
- creating beads directory: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/62627e393fef377b.
Report an issue: GitHub.