gastownhall/beads · error
workspacegate: open gate %s: %w
Error message
workspacegate: open gate %s: %w
What it means
Acquire in internal/workspacegate could not open (or create) the gate lock file at g.path with O_CREATE|O_RDWR and 0o600 permissions. The underlying os.PathError is wrapped with %w so the caller can inspect it via errors.Is/As. This is a filesystem-level failure before any advisory locking is attempted, so no lock state was changed.
Source
Thrown at internal/workspacegate/gate.go:392
// Tolerate a nil context rather than panicking on ctx.Err below: gate
// acquisition sits on CLI plumbing paths (cobra hooks, migrate helpers)
// that tests and embedders invoke directly without the process-level
// signal context, and a nil-deref here kills the whole test binary.
if ctx == nil {
ctx = context.Background()
}
if err := ctx.Err(); err != nil {
return nil, fmt.Errorf("workspacegate: acquiring %s: %w", g.path, err)
}
poll := opts.PollInterval
if poll <= 0 {
poll = 100 * time.Millisecond
}
deadline := time.Now().Add(opts.Wait)
f, err := os.OpenFile(g.path, os.O_CREATE|os.O_RDWR, 0o600)
if err != nil {
return nil, fmt.Errorf("workspacegate: open gate %s: %w", g.path, err)
}
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) {View on GitHub (pinned to 71377f2769)
Solutions
- Check the gate path: ensure its parent directory exists (os.MkdirAll) and the path itself is not a directory.
- Verify the current user can create/write the file: run ls -ld on the path and parent, or touch the file manually.
- If the gate file exists but is owned by another user, remove it or chown/chmod it so the process can open it O_RDWR.
- If running in a container/sandbox, mount or configure a writable directory for the gate path.
Example fix
// before
g := workspacegate.New("/nonexistent/dir/gate.lock")
h, err := g.Acquire(ctx, workspacegate.Exclusive, opts)
// after
os.MkdirAll("/var/run/myapp", 0o755)
g := workspacegate.New("/var/run/myapp/gate.lock")
h, err := g.Acquire(ctx, workspacegate.Exclusive, opts) Defensive patterns
Strategy: try-catch
Validate before calling
if fi, err := os.Stat(dirOfGatePath); err != nil || !fi.IsDir() {
return fmt.Errorf("gate directory unavailable: %s", dirOfGatePath)
}
if fi, err := os.Stat(gatePath); err == nil && fi.IsDir() {
return fmt.Errorf("gate path is a directory: %s", gatePath)
} Try / catch
h, err := g.Acquire(ctx, mode, opts)
var pe *os.PathError
if err != nil {
if errors.As(err, &pe) && errors.Is(pe.Err, os.ErrPermission) {
return fmt.Errorf("cannot access gate %s as current user", pe.Path)
}
return err
} Prevention
- Create the gate directory with os.MkdirAll at startup before any Acquire.
- Run all processes that share a gate as the same user, or widen the 0o600 mode deliberately.
- Keep gate files on writable local storage.
When it happens
Trigger: Calling Acquire (via mustAcquire) where os.OpenFile on the gate path fails: the parent directory does not exist, the path is a directory, or the process lacks write permission on an existing gate file.
Common situations: Gate path pointing into a nonexistent or read-only directory; running under a different uid than the gate file owner (0o600 means only the owner can open it read-write); gate path accidentally configured as a directory; sandboxed CI containers with restricted mounts.
Related errors
- workspacegate: probe %s: %w
- workspacegate: lock %s: %w
- failed to remove %s: %w
- creating beads directory: %w
- opening lock file: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/131c6fb3308023ad.
Report an issue: GitHub.