gastownhall/beads · error · issueops.ErrValidation

%w: apply batch item %d reuses key %q, already declared by i

Error message

%w: apply batch item %d reuses key %q, already declared by item %d

What it means

Each create item may declare a Key so later items can reference it; keys must be unique within the batch. This error fires when two create items declare the same Key, since the key-to-item map would be ambiguous. Wraps issueops.ErrValidation.

Source

Thrown at internal/storage/batch_apply.go:157

		if !matches {
			return nil, fmt.Errorf("%w: apply batch item %d is kind %q but carries another kind's payload",
				issueops.ErrValidation, i, item.Kind)
		}
		if item.Create == nil {
			continue
		}
		if item.Create.Issue == nil {
			return nil, fmt.Errorf("%w: apply batch item %d requires an issue", issueops.ErrValidation, i)
		}
		if len(item.Create.Issue.Comments) > 0 || len(item.Create.Issue.Dependencies) > 0 {
			return nil, fmt.Errorf("%w: apply batch item %d must not carry comments or dependencies on the issue; edges are their own items",
				issueops.ErrValidation, i)
		}
		if item.Create.Key == "" {
			continue
		}
		if prior, dup := keyIndex[item.Create.Key]; dup {
			return nil, fmt.Errorf("%w: apply batch item %d reuses key %q, already declared by item %d",
				issueops.ErrValidation, i, item.Create.Key, prior)
		}
		keyIndex[item.Create.Key] = i
	}
	return keyIndex, nil
}

// planApplyBatchItem validates one item's refs, guards and edge metadata, and
// records what it touches for the items after it.
func planApplyBatchItem(item *issueops.ApplyItem, index int, keyIndex map[string]int, touched map[string]bool) error {
	switch item.Kind {
	case issueops.ItemCreate:
		return planApplyBatchCreate(item.Create, index, keyIndex, touched)
	case issueops.ItemUpdate:
		return planApplyBatchUpdate(item.Update, index, keyIndex, touched)
	case issueops.ItemClose:
		return planApplyBatchClose(item.Close, index, keyIndex, touched)
	case issueops.ItemDepAdd:

View on GitHub (pinned to 71377f2769)

Solutions

  1. Make each Create.Key unique (append a suffix/counter)
  2. Remove the Key from the duplicate item if nothing references it (empty keys are allowed)
  3. Track used keys in the generator and skip or rename duplicates

Example fix

// before
for _, it := range issues { items = append(items, mkCreate("issue", it)) } // key always "issue"
// after
for i, it := range issues { items = append(items, mkCreate(fmt.Sprintf("issue-%d", i), it)) }
Defensive patterns

Strategy: validation

Validate before calling

seen := map[string]bool{}
for i, it := range items {
  if it.Create != nil && it.Create.Key != "" {
    if seen[it.Create.Key] {
      return fmt.Errorf("item %d reuses key %q", i, it.Create.Key)
    }
    seen[it.Create.Key] = true
  }
}

Try / catch

if err := store.PlanApplyBatch(plan); err != nil {
  if errors.Is(err, issueops.ErrValidation) && strings.Contains(err.Error(), "reuses key") {
    // regenerate unique keys and rebuild the plan
  }
  return err
}

Prevention

When it happens

Trigger: PlanApplyBatch with two create items both setting Create.Key to the same string (e.g. two items with Key "bd-1"); the second one triggers the error.

Common situations: Reusing a slug/key across loop iterations without templating it; appending a fixture batch twice; generating keys from a field that isn't unique in the source data.

Related errors


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