gastownhall/beads · error

merge slot not found: %s

Error message

merge slot not found: %s

What it means

MergeSlotReleaseImpl could not fetch the merge slot bead (GetIssue returned an error or nil) while releasing it. The slot ID is derived from the issue_prefix config (e.g. gt-merge-slot), so this usually means the slot bead does not exist under the expected ID.

Source

Thrown at internal/storage/merge_slot.go:161

			return nil
		},
	)
	if err != nil {
		return nil, err
	}
	return &result, nil
}

// MergeSlotReleaseImpl is the shared implementation of Storage.MergeSlotRelease.
func MergeSlotReleaseImpl(ctx context.Context, s Storage, holder, actor string) error {
	slotID := MergeSlotID(ctx, s)

	return s.RunInTransaction(ctx,
		fmt.Sprintf("bd: release merge slot %s", slotID),
		func(tx Transaction) error {
			slot, err := tx.GetIssue(ctx, slotID)
			if err != nil || slot == nil {
				return fmt.Errorf("merge slot not found: %s", slotID)
			}

			meta := parseSlotMeta(slot)

			if holder != "" && meta.Holder != holder {
				return fmt.Errorf("slot held by %s, not %s", meta.Holder, holder)
			}

			if slot.Status == types.StatusOpen {
				// Already released; idempotent.
				return nil
			}

			newMeta := slotMeta{Waiters: meta.Waiters}
			metaStr, err := encodeSlotMeta(newMeta)
			if err != nil {
				return fmt.Errorf("failed to encode slot metadata: %w", err)
			}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Run 'bd merge-slot create' to create the slot bead, then retry the release
  2. Verify the slot exists under the expected ID (prefix + '-merge-slot') with bd show
  3. Check the issue_prefix config has not changed since the slot was created
  4. If the slot is gone but work completed, the release can often be skipped — the slot is free by definition

Example fix

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

Strategy: validation

Validate before calling

slotID := MergeSlotID(ctx, store)
if slot, err := store.GetIssue(ctx, slotID); err != nil || slot == nil {
    if _, err := store.MergeSlotCreate(ctx, actor); err != nil { return err }
}

Type guard

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

Try / catch

if err := store.MergeSlotRelease(ctx, holder, actor); err != nil {
    var nf *NotFoundError
    if errors.As(err, &nf) || strings.Contains(err.Error(), "merge slot not found") {
        // Slot gone = lock already free; recreate for future use and continue.
        _, cerr := store.MergeSlotCreate(ctx, actor)
        return cerr
    }
    return err
}

Prevention

When it happens

Trigger: Calling MergeSlotRelease (bd merge-slot release) when the slot bead was never created, was deleted manually, or the issue_prefix config changed so a different slot ID is computed than the one that exists.

Common situations: Fresh database where 'bd merge-slot create' was never run; someone deleted the internal merge-slot issue; config prefix changed between create and release (e.g. migrating from bd- to gt- prefix).

Related errors


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