gastownhall/beads · error

create batch item %d: %w

Error message

create batch item %d: %w

What it means

CreateBatchItemError wraps an underlying batch refusal and prefixes it with 'create batch item N:' so the caller knows which item caused the store to refuse the batch. Per its contract the item index appears only in the message, never structured elsewhere. It is a contextual wrapper — the real cause is the wrapped error (extractable via errors.Unwrap or errors.As/Is).

Source

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

	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
// promises the index appears in the message and nowhere else.
func CreateBatchItemError(index int, err error) error {
	return fmt.Errorf("create batch item %d: %w", index, err)
}

// CreateBatchCommitMessage is the history entry a batch records: the caller's
// own label when it supplied one, otherwise a default naming how much landed.
//
// IT NAMES A COUNT AND NEVER AN ID: a create batch's ids are new, there can be
// hundreds from one file, and an entry naming them all is the diff written twice.
//
// An all-ephemeral batch writes only to the dolt-ignored wisp tables, so the
// store-backed bodies stage nothing and record no entry whatever this returns —
// but the unit-of-work backend reads "" as "roll this attempt back", so a
// wisp-only batch must still hand it a message or the wisps it created are
// discarded. That is the same trap CloseBatchCommitMessage documents.
func CreateBatchCommitMessage(request publicops.CreateBatchRequest, result publicops.CreateBatchResult) string {
	durable, ephemeral := 0, 0
	for _, issue := range result.Issues {
		if IsWisp(issue) {
			ephemeral++

View on GitHub (pinned to 71377f2769)

Solutions

  1. Read the wrapped error after 'create batch item N:' to learn the actual refusal and fix the item at that index
  2. Use errors.Is/errors.As on the returned error to match sentinel causes like storage.ErrValidation or storage.ErrAlreadyExists
  3. Retry the whole batch after fixing the offending item; the batch is atomic so nothing partial was written

Example fix

// before
if err := ExecuteCreateBatch(ctx, req); err != nil {
    return err
}
// after
if err := ExecuteCreateBatch(ctx, req); err != nil {
    var idxErr error
    if errors.As(err, &idxErr) { /* inspect wrapped cause for item N */ }
    return err
}
Defensive patterns

Strategy: try-catch

Try / catch

err := ExecuteCreateBatch(ctx, req)
if err != nil {
    var target error
    if errors.As(err, &target) && errors.Is(target, storage.ErrAlreadyExists) {
        // handle duplicate named by 'create batch item N:'
    } else if errors.Is(err, storage.ErrValidation) {
        // fix item at index parsed from message
    }
    return err
}

Prevention

When it happens

Trigger: ExecuteCreateBatch hits a per-item failure (validation, duplicate ID, prefix mismatch, etc.) during batch creation; the store wraps that cause with CreateBatchItemError(index, err).

Common situations: Importing a JSONL file where one record has a bad field; a batch where a single issue ID collides with an existing issue; mixed valid/invalid items in one atomic batch.

Related errors


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