gastownhall/beads · error
merge-slot acquire: holder must not be empty
Error message
merge-slot acquire: holder must not be empty
What it means
MergeSlotAcquireImpl requires a non-empty holder identity because the slot's whole purpose is recording WHO holds the exclusive merge lock. An empty holder would make the slot un-releasable and audit-invisible, so it is rejected immediately with this static validation error before any transaction runs.
Source
Thrown at internal/storage/merge_slot.go:85
if err != nil || slot == nil {
return nil, fmt.Errorf("merge slot not found: %s (run 'bd merge-slot create' first): %w",
slotID, ErrNotFound)
}
meta := parseSlotMeta(slot)
return &MergeSlotStatus{
SlotID: slotID,
Available: slot.Status == types.StatusOpen,
Holder: meta.Holder,
Waiters: meta.Waiters,
}, nil
}
// MergeSlotAcquireImpl is the shared implementation of Storage.MergeSlotAcquire.
// It uses RunInTransaction to ensure atomic check-and-set, preventing two
// agents from simultaneously acquiring the slot.
func MergeSlotAcquireImpl(ctx context.Context, s Storage, holder, actor string, wait bool) (*MergeSlotResult, error) {
if holder == "" {
return nil, fmt.Errorf("merge-slot acquire: holder must not be empty")
}
slotID := MergeSlotID(ctx, s)
var result MergeSlotResult
result.SlotID = slotID
err := s.RunInTransaction(ctx,
fmt.Sprintf("bd: acquire merge slot %s for %s", slotID, holder),
func(tx Transaction) error {
slot, err := tx.GetIssue(ctx, slotID)
if err != nil || slot == nil {
return fmt.Errorf("merge slot not found: %s (run 'bd merge-slot create' first)", slotID)
}
meta := parseSlotMeta(slot)
result.Holder = meta.Holder
if slot.Status != types.StatusOpen {View on GitHub (pinned to 71377f2769)
Solutions
- Pass a non-empty holder identifier (agent name, hostname, session ID) to MergeSlotAcquire / the CLI flag
- In scripts, default or validate the identity: holder="${AGENT_NAME:-$(hostname)}" and fail fast if still empty
- Fix the CI/bootstrap configuration so the agent identity env var is exported before the acquire call
Example fix
// before
holder := os.Getenv("AGENT_NAME") // empty in CI
res, err := store.MergeSlotAcquire(ctx, holder, actor, true)
// after
holder := os.Getenv("AGENT_NAME")
if holder == "" { holder, _ = os.Hostname() }
if holder == "" { return errors.New("cannot acquire merge slot: no agent identity") }
res, err := store.MergeSlotAcquire(ctx, holder, actor, true) Defensive patterns
Strategy: validation
Validate before calling
if holder == "" {
return errors.New("merge-slot acquire: holder must not be empty")
}
// then call the API Type guard
func validHolder(holder string) bool { return strings.TrimSpace(holder) != "" } Prevention
- Validate holder identity at CLI/entrypoint before calling storage
- Default to os.Hostname() or agent session ID when identity env vars are unset
- Fail fast in CI if AGENT_NAME/identity variables are empty
- Trim whitespace — a blank-string holder is as bad as empty
When it happens
Trigger: Calling MergeSlotAcquire (bd merge-slot acquire) with an empty holder string — e.g. an unset agent-name variable, a CLI flag omitted, or an environment variable like $HOSTNAME/$AGENT_ID empty in the calling shell or CI job.
Common situations: CI pipeline where an agent identity env var was never exported; script interpolating an unset variable into --holder ""; misconfigured agent bootstrap that skips identity assignment.
Understand the failure class
Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.
Related errors
- --waits-for-gate requires --waits-for (no spawner ID specifi
- no %s provided (%s)
- --source-db and --output are required
- no store is open for this workspace
- not found
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/ed671c557f89effc.
Report an issue: GitHub.