gastownhall/beads · warning

merge slot not found: %s (run 'bd merge-slot create' first)

Error message

merge slot not found: %s (run 'bd merge-slot create' first)

What it means

Inside the acquire transaction, MergeSlotAcquireImpl fetches the slot bead; if GetIssue errors or returns nil, the tx returns this error and the acquire aborts. Unlike MergeSlotCheckImpl's wrapped variant, this is the plain (non-%w) form used inside the transaction closure — same root cause: the slot bead does not exist under the ID derived from issue_prefix.

Source

Thrown at internal/storage/merge_slot.go:97

// 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 {
				// Slot is held.
				if wait {
					alreadyWaiting := false
					for _, w := range meta.Waiters {
						if w == holder {
							alreadyWaiting = true
							break
						}
					}
					if !alreadyWaiting {
						meta.Waiters = append(meta.Waiters, holder)
					}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Run 'bd merge-slot create' first (idempotent), then retry the acquire
  2. Check issue_prefix config consistency across agents — all must derive the same slot ID
  3. Search the store for '<prefix>-merge-slot' or 'gt:slot' labeled beads to find a slot under an old prefix and recreate/migrate it
  4. If GetIssue is actually failing (not missing), inspect DB connectivity and tx health in the wrapped error from RunInTransaction

Example fix

// before
res, err := store.MergeSlotAcquire(ctx, holder, actor, true) // fails: no slot
// after
if _, err := store.MergeSlotCreate(ctx, actor); err != nil { return err } // idempotent
res, err := store.MergeSlotAcquire(ctx, holder, actor, true)
Defensive patterns

Strategy: fallback

Validate before calling

// ensure slot exists before acquiring (idempotent)
if _, err := store.MergeSlotCreate(ctx, actor); err != nil { return err }

Type guard

func slotReady(ctx context.Context, s storage.Storage) bool {
	slot, err := s.GetIssue(ctx, storage.MergeSlotID(ctx, s))
	return err == nil && slot != nil
}

Try / catch

res, err := store.MergeSlotAcquire(ctx, holder, actor, wait)
if err != nil && strings.Contains(err.Error(), "merge slot not found") {
	if _, cerr := store.MergeSlotCreate(ctx, actor); cerr != nil { return cerr }
	res, err = store.MergeSlotAcquire(ctx, holder, actor, wait)
}

Prevention

When it happens

Trigger: Calling MergeSlotAcquire before the slot was ever created, after the slot bead was deleted, or after an issue_prefix change moved MergeSlotID to a nonexistent ID; also when the underlying GetIssue inside RunInTransaction fails (connection/tx error) — the code cannot distinguish and reports not-found.

Common situations: Fresh repo where 'bd merge-slot create' was skipped; prefix renamed so agents look for bd-merge-slot while the actual bead is gt-merge-slot; slot bead deleted during a cleanup sweep that didn't know it was infrastructure.

Related errors


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