gastownhall/beads · warning
workspacegate: waiting for %s: %w
Error message
workspacegate: waiting for %s: %w
What it means
While Acquire was sleeping between lock retries, its context was cancelled or timed out; it wraps ctx.Err() with the gate path. The file descriptor is closed and no lock is held. This reports why the wait was abandoned, not a lock failure.
Source
Thrown at internal/workspacegate/gate.go:436
}
}
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 {
sleep = remaining
}
select {
case <-ctx.Done():
_ = f.Close()
return nil, fmt.Errorf("workspacegate: waiting for %s: %w", g.path, ctx.Err())
case <-time.After(sleep):
}
}
}
// writeInfo records the advisory exclusive-holder sidecar. Failures are
// deliberately swallowed: diagnostics must never block the operation that
// already holds the authoritative lock. The write goes to an O_EXCL temp
// file renamed into place, which (a) never follows a pre-planted symlink
// at either path — plain WriteFile would truncate the symlink's target —
// and (b) is atomic, so concurrent readers cannot see torn JSON.
func (g Gate) writeInfo(reason string) {
host, _ := os.Hostname()
data, err := json.Marshal(Info{
PID: os.Getpid(),
Hostname: host,
Reason: reason,
StartedAt: time.Now().UTC(),View on GitHub (pinned to 71377f2769)
Solutions
- Check errors.Is(err, context.DeadlineExceeded) vs context.Canceled to decide whether to extend the deadline or treat it as user abort.
- Increase the context timeout/deadline to exceed the expected holder duration.
- Ensure the context stays alive for the whole Acquire (do not tie it to a short-lived request if the wait can be long).
- Use the OnWait callback to log contention early so operators can release the gate before the context dies.
Example fix
// before
h, err := g.Acquire(ctx, workspacegate.Exclusive, workspacegate.AcquireOpts{Wait: time.Minute})
// after
acqCtx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()
h, err := g.Acquire(acqCtx, workspacegate.Exclusive, workspacegate.AcquireOpts{Wait: time.Minute}) Defensive patterns
Strategy: try-catch
Validate before calling
if ctx.Err() != nil {
return fmt.Errorf("context already done before acquire: %w", ctx.Err())
}
if dl, ok := ctx.Deadline(); ok && time.Until(dl) < expectedHolderTime {
// widen the deadline before attempting
} Try / catch
h, err := g.Acquire(ctx, mode, opts)
if err != nil {
switch {
case errors.Is(err, context.Canceled):
return fmt.Errorf("acquire aborted by caller")
case errors.Is(err, context.DeadlineExceeded):
return fmt.Errorf("acquire wait budget exhausted: %w", err)
default:
return err
}
} Prevention
- Set context deadlines longer than opts.Wait so cancellation is intentional, not accidental.
- Keep the acquire context independent of short-lived request contexts.
- Handle SIGINT by cancelling the context so the error path is deterministic.
- Use the OnWait hook to release/abort early before the context dies.
When it happens
Trigger: Calling Acquire with a ctx that is cancelled, or whose deadline/timeout expires, while the gate is busy and the function is inside the select on ctx.Done() between retry polls.
Common situations: HTTP request contexts timing out while waiting on a long-held gate; CLI processes interrupted (SIGINT) cancelling the context; a parent goroutine cancelling work because an earlier step failed; overly short context deadlines relative to opts.Wait.
Related errors
- workspacegate: acquiring %s: %w
- interrupted waiting for cache lock on %s: %w
- workspacegate: open gate %s: %w
- workspacegate: lock %s: %w
- workspacegate: %s (%s mode) held by %s: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/cb19f890268b5dbc.
Report an issue: GitHub.