gastownhall/beads · error · storage.ErrValidation

%w: create batch item %d requires an issue

Error message

%w: create batch item %d requires an issue

What it means

Validation error from ValidateCreateBatchRequest: every item in a CreateBatchRequest must carry a non-nil Issue pointer. It wraps storage.ErrValidation and runs before any transaction opens, so a batch refused here has provably written nothing. The library throws it to catch callers building batch items without assigning the issue payload.

Source

Thrown at internal/storage/issueops/create_batch.go:45

// ValidateCreateBatchRequest applies the request rules every BatchCreator
// implementation shares. It runs BEFORE any transaction opens: everything it
// checks is knowable from the request alone, so a batch refused here has
// provably written nothing.
//
// Per-item CONTENT rules are not here. They need the workspace's configured
// prefix, statuses and types, so they run inside the transaction through the
// same PreparePublicCreateRequest a single create runs.
func ValidateCreateBatchRequest(request publicops.CreateBatchRequest) error {
	if request.Actor == "" {
		return fmt.Errorf("%w: create batch requires an actor", storage.ErrValidation)
	}
	if len(request.Items) == 0 {
		return fmt.Errorf("%w: create batch requires at least one item", storage.ErrValidation)
	}
	for i, item := range request.Items {
		if item.Issue == nil {
			return fmt.Errorf("%w: create batch item %d requires an issue", storage.ErrValidation, i)
		}
	}
	return nil
}

// CreateBatchItemRequest projects one item onto the single-create request the
// shared preparation and validation speak. Both front doors and both bodies read
// an item through it, so no item restates CreateRequest's field rules.
func CreateBatchItemRequest(request publicops.CreateBatchRequest, item publicops.BatchCreateItem) publicops.CreateRequest {
	return publicops.CreateRequest{
		Actor:         request.Actor,
		Issue:         item.Issue,
		Dependencies:  item.Dependencies,
		ForceIDPrefix: request.ForceIDPrefix,
	}
}

// CreateBatchItemError names the item a batch refusal came from. The role

View on GitHub (pinned to 71377f2769)

Solutions

  1. Find item index N named in the message and set its Issue field to a non-nil *types.Issue before calling ExecuteCreateBatch
  2. Filter nil issues out of the items slice before building the CreateBatchRequest
  3. Run ValidateCreateBatchRequest (or your own nil-check loop) client-side before invoking the batch

Example fix

// before
items := []publicops.BatchCreateItem{{Issue: &a}, {}}
// after
items := []publicops.BatchCreateItem{{Issue: &a}, {Issue: &b}}
Defensive patterns

Strategy: validation

Validate before calling

for i, item := range req.Items {
    if item.Issue == nil {
        return fmt.Errorf("item %d has nil Issue", i)
    }
}
// then call ExecuteCreateBatch

Type guard

func hasIssue(item publicops.BatchCreateItem) bool { return item.Issue != nil }

Prevention

When it happens

Trigger: Calling any BatchCreator's ExecuteCreateBatch with request.Items[i].Issue == nil — e.g. constructing BatchCreateItem{Issue: nil} or appending an item before filling its Issue field.

Common situations: Programmatically building batches from parsed input where an entry fails to map to an issue; partial struct initialization in loops; nil entries from a slice of optional issues not filtered before batching.

Related errors


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