{"record":{"id":"c1bf2eca74c56946","repo":"gastownhall/beads","slug":"errbusy","errorCode":"ErrBusy","errorMessage":"workspace gate busy","messagePattern":"workspace gate busy","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"internal/workspacegate/gate.go","lineNumber":95,"sourceCode":"\t// Exclusive is held by maintenance operations. It conflicts with\n\t// every other holder, shared or exclusive. See the starvation note\n\t// on Shared: nothing here prevents shared holders from starving an\n\t// Exclusive acquirer.\n\tExclusive\n)\n\nfunc (m Mode) String() string {\n\tif m == Exclusive {\n\t\treturn \"exclusive\"\n\t}\n\treturn \"shared\"\n}\n\n// ErrBusy is returned when the gate cannot be acquired within the\n// configured wait budget. Use errors.Is; the wrapped error carries holder\n// diagnostics when available. The public alias for external consumers is\n// beads.ErrGateBusy.\nvar ErrBusy = errors.New(\"workspace gate busy\")\n\n// Options tunes acquisition. The zero value means a single non-blocking\n// attempt with no diagnostics callback.\ntype Options struct {\n\t// Wait bounds how long Acquire keeps retrying after the first busy\n\t// attempt. Zero or negative means exactly one non-blocking try.\n\t// The underlying blocking lock primitives have no deadline support,\n\t// so waiting is implemented as timed polling of the non-blocking\n\t// primitive. This is not a fairness guarantee: see the starvation\n\t// note on Exclusive — a Wait on an Exclusive acquisition can still\n\t// exhaust its budget against a rolling sequence of Shared holders\n\t// that each individually released before it, none of which had any\n\t// signal that an exclusive acquirer was waiting.\n\tWait time.Duration\n\t// PollInterval is the retry cadence while waiting (default 100ms).\n\tPollInterval time.Duration\n\t// Reason is recorded in the advisory holder-info sidecar on\n\t// exclusive acquisition so blocked commands can say who is holding","sourceCodeStart":77,"sourceCodeEnd":113,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/workspacegate/gate.go#L77-L113","documentation":"ErrBusy is returned when a workspace gate cannot be acquired within the configured wait budget. The wrapped error carries holder diagnostics when available, and errors.Is should be used for classification. The public alias for external consumers is beads.ErrGateBusy.","triggerScenarios":"Calling Acquire (directly or via acquireCommandWorkspaceGates) while another process holds the gate and the wait budget (Options.Wait) expires; a single non-blocking try (zero/negative Wait) hitting a held gate; streaming events (streamEvents) contending for the same gate.","commonSituations":"Two bd commands running concurrently in the same workspace (e.g. a background daemon and a CLI command); a crashed process leaving stale holder state; CI jobs racing on a shared workspace checkout.","solutions":["Wait for the other holder to finish and retry; the wrapped error names the holder for diagnosis.","Increase Options.Wait so Acquire retries within a longer budget instead of a single non-blocking try.","Investigate stale holders (crashed processes) and clear them before retrying.","Classify with errors.Is(err, workspacegate.ErrBusy) / beads.ErrGateBusy and implement bounded retry with backoff in the caller."],"exampleFix":"// before\ng, err := gate.Acquire(ctx) // zero Wait: single try, busy\n\n// after\ng, err := gate.Acquire(ctx, workspacegate.Options{Wait: 5 * time.Second})\nif errors.Is(err, workspacegate.ErrBusy) {\n    // inspect holder diagnostics in wrapped error, retry or report\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"g, err := gate.Acquire(ctx, workspacegate.Options{Wait: 5 * time.Second})\nif errors.Is(err, workspacegate.ErrBusy) {\n    // read holder diagnostics from wrapped error, back off and retry\n}","preventionTips":["Set a nonzero Options.Wait budget for interactive commands.","Serialize long-running daemon work away from CLI commands.","Alert on stale holders after crashes and clean them up.","Use the public alias beads.ErrGateBusy for classification."],"tags":["concurrency","locking","retry"],"backgroundTag":"lock-contention","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}