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

  1. Pass a non-empty holder identifier (agent name, hostname, session ID) to MergeSlotAcquire / the CLI flag
  2. In scripts, default or validate the identity: holder="${AGENT_NAME:-$(hostname)}" and fail fast if still empty
  3. 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

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


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/ed671c557f89effc. Report an issue: GitHub.