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
- Set item.Kind to match the one populated payload field (or vice versa)
- Nil out the extra payload so exactly one of Create/Update/Close/DepAdd is set, matching Kind
- 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
- Use per-kind constructor helpers that set Kind and payload together
- Never assign more than one payload field on an ApplyItem
- Add a pre-submit validation pass mirroring the planner's checks
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
- %w: apply batch item %d requires an issue
- %w: apply batch item %d must not carry comments or dependenc
- %w: apply batch item %d reuses key %q, already declared by i
- %w: apply batch item %d has a metadata_ref with an empty key
- %w: apply batch item %d: %s cannot depend on itself
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/b6570f239b982bb8.
Report an issue: GitHub.