gastownhall/beads · error · issueops.ErrValidation

%w: apply batch item %d requires an issue

Error message

%w: apply batch item %d requires an issue

What it means

A create-kind batch item must embed an Issue payload; this error fires when item.Create is non-nil but item.Create.Issue is nil. The planner needs the issue row to know what to insert, and there is nothing to plan without it. Wraps issueops.ErrValidation.

Source

Thrown at internal/storage/batch_apply.go:147

			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)
		}
		keyIndex[item.Create.Key] = i
	}
	return keyIndex, nil
}

// planApplyBatchItem validates one item's refs, guards and edge metadata, and

View on GitHub (pinned to 71377f2769)

Solutions

  1. Assign a complete CreateItem.Issue before calling PlanApplyBatch
  2. Check the item construction path for a branch that skips setting Issue
  3. Assert non-nil Issue in the code that builds the batch

Example fix

// before
item := issueops.ApplyItem{Kind: issueops.ItemCreate, Create: &issueops.CreateItem{Key: "bd-1"}}
// after
item := issueops.ApplyItem{Kind: issueops.ItemCreate, Create: &issueops.CreateItem{Key: "bd-1", Issue: &types.Issue{Title: "Fix login", Status: "open"}}}
Defensive patterns

Strategy: validation

Validate before calling

for i, it := range items {
  if it.Kind == issueops.ItemCreate && it.Create != nil && it.Create.Issue == nil {
    return fmt.Errorf("create item %d has no Issue", i)
  }
}

Type guard

func hasIssue(it *issueops.CreateItem) bool { return it != nil && it.Issue != nil }

Try / catch

if err := store.PlanApplyBatch(plan); err != nil {
  if errors.Is(err, issueops.ErrValidation) && strings.Contains(err.Error(), "requires an issue") {
    // rebuild the plan with the Issue populated
  }
  return err
}

Prevention

When it happens

Trigger: PlanApplyBatch with an item where Kind is ItemCreate and Create != nil, but Create.Issue was never assigned (zero-value CreateItem or Issue field forgotten).

Common situations: Constructing CreateItem{Key: "x"} and populating the issue later in code that errored or was skipped; unmarshaling partial JSON where the issue object was absent.

Related errors


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