gastownhall/beads · error · issueops.ErrValidation

%w: apply batch item %d is kind %q but carries another kind'

Error message

%w: apply batch item %d is kind %q but carries another kind's payload

What it means

PlanApplyBatch validates that each ApplyItem's Kind matches exactly one non-nil payload (Create/Update/Close/DepAdd). This error means the item declared one kind but the payload pointer set belongs to a different kind, so the planner cannot tell what operation the item represents. It wraps issueops.ErrValidation, so callers can detect it via errors.Is.

Source

Thrown at internal/storage/batch_apply.go:140

		if payloads != 1 {
			return nil, fmt.Errorf("%w: apply batch item %d must carry exactly one payload, got %d",
				issueops.ErrValidation, i, payloads)
		}
		var matches bool
		switch item.Kind {
		case issueops.ItemCreate:
			matches = item.Create != nil
		case issueops.ItemUpdate:
			matches = item.Update != nil
		case issueops.ItemClose:
			matches = item.Close != nil
		case issueops.ItemDepAdd:
			matches = item.DepAdd != nil
		default:
			return nil, fmt.Errorf("%w: apply batch item %d has unknown kind %q", issueops.ErrValidation, i, item.Kind)
		}
		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)

View on GitHub (pinned to 71377f2769)

Solutions

  1. Set item.Kind to match the one populated payload field (or vice versa)
  2. Nil out the extra payload so exactly one of Create/Update/Close/DepAdd is set, matching Kind
  3. Write a small builder helper per kind (NewItemCreate/NewItemDepAdd) that sets Kind and payload together

Example fix

// before
item := issueops.ApplyItem{Kind: issueops.ItemUpdate, DepAdd: &edge}
// after
item := issueops.ApplyItem{Kind: issueops.ItemDepAdd, DepAdd: &edge}
Defensive patterns

Strategy: validation

Validate before calling

for i, it := range items {
  var n int
  if it.Create != nil { n++ }
  if it.Update != nil { n++ }
  if it.Close != nil { n++ }
  if it.DepAdd != nil { n++ }
  if n != 1 { return fmt.Errorf("item %d: %d payloads", i, n) }
  var ok bool
  switch it.Kind {
  case issueops.ItemCreate: ok = it.Create != nil
  case issueops.ItemUpdate: ok = it.Update != nil
  case issueops.ItemClose: ok = it.Close != nil
  case issueops.ItemDepAdd: ok = it.DepAdd != nil
  default: return fmt.Errorf("item %d: unknown kind", i)
  }
  if !ok { return fmt.Errorf("item %d: kind/payload mismatch", i) }
}

Type guard

func kindMatchesPayload(it issueops.ApplyItem) bool {
  switch it.Kind {
  case issueops.ItemCreate: return it.Create != nil
  case issueops.ItemUpdate: return it.Update != nil
  case issueops.ItemClose: return it.Close != nil
  case issueops.ItemDepAdd: return it.DepAdd != nil
  }
  return false
}

Try / catch

if err := store.PlanApplyBatch(plan); err != nil {
  if errors.Is(err, issueops.ErrValidation) {
    // fix the item's kind/payload pairing and rebuild the plan
  }
  return err
}

Prevention

When it happens

Trigger: Calling PlanApplyBatch (via planApplyBatchKeys) with an item like {Kind: ItemCreate, Update: &UpdateItem{...}} — the kind label and the populated payload struct disagree.

Common situations: Copy-pasting item construction code and editing the payload but not the Kind field; building items from JSON/config where kind and payload come from different sources; switching an item from update to create and forgetting to nil the old payload.

Related errors


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