gastownhall/beads · error
workspacegate: acquiring %s: %w
Error message
workspacegate: acquiring %s: %w
What it means
Gate.Acquire checks ctx.Err() before attempting the lock; if the context is already canceled or its deadline has expired, acquisition is refused immediately with the context error wrapped and the gate path prefixed. This is fail-fast: a dead context can never hold a gate, so no lock attempt is made.
Source
Thrown at internal/workspacegate/gate.go:382
// Acquire takes the gate in the given mode, polling until Options.Wait is
// exhausted or ctx is done. The returned handle's file descriptor is not
// inherited by spawned children (Go opens files close-on-exec on Unix and
// non-inheritable on Windows), so a dolt child outliving its bd parent
// does not keep the gate held.
func (g Gate) Acquire(ctx context.Context, mode Mode, opts Options) (*Handle, error) {
if g.path == "" {
return nil, errors.New("workspacegate: zero Gate; use ForWorkspace/ForPhysicalRoot")
}
// 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 := falseView on GitHub (pinned to 71377f2769)
Solutions
- Pass a live context — context.Background() (or a fresh signal context) for maintenance operations that must run to completion.
- Check ctx.Err() before calling Acquire and create a fresh context if the old one is spent.
- For background work, use context.WithoutCancel(ctx) (Go 1.21+) to drop cancellation while keeping values.
- If a timeout is the cause, extend the WithTimeout budget to cover gate waiting (Options.Wait) plus the operation itself.
Example fix
// before ctx, cancel := context.WithTimeout(reqCtx, 5*time.Second) defer cancel() // later, after handler returns: h, err := g.Acquire(ctx, workspacegate.Exclusive, opts) // ctx already done // after bg := context.WithoutCancel(reqCtx) h, err := g.Acquire(bg, workspacegate.Exclusive, opts)
Defensive patterns
Strategy: validation
Validate before calling
if err := ctx.Err(); err != nil {
return fmt.Errorf("cannot acquire gate: %w", err)
}
h, err := g.Acquire(ctx, workspacegate.Shared, workspacegate.Options{}) Try / catch
h, err := g.Acquire(ctx, mode, opts)
if err != nil {
if errors.Is(err, context.Canceled) {
return nil // caller canceled; clean exit
}
if errors.Is(err, context.DeadlineExceeded) {
return fmt.Errorf("gate wait budget exhausted: %w", err)
}
return err
} Prevention
- Never reuse an HTTP request or command context after its scope ends.
- Use context.WithoutCancel for background maintenance that must finish.
- Give WithTimeout budgets enough headroom for Options.Wait plus the operation.
- Check ctx.Err() before long setup steps that precede Acquire.
When it happens
Trigger: Calling g.Acquire (or AcquireAll) with a context that was already canceled, or whose deadline expired before the call — e.g. reusing a per-request context after the request finished, or a signal-triggered cancel from cobra's signal context firing before acquisition.
Common situations: CLI commands whose signal context was canceled by Ctrl-C racing startup; passing an http.Request's context to a background maintenance task that runs after the handler returns; a WithTimeout whose budget was consumed by earlier setup; unit tests passing a canceled context.
Related errors
- interrupted waiting for cache lock on %s: %w
- workspacegate: waiting for %s: %w
- timeout waiting for cache lock on %s
- util: creating lock directory: %w
- util: opening lock file: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/262aab348b4855c3.
Report an issue: GitHub.