gastownhall/beads · warning

%w: close batch item %d requires an issue ID

Error message

%w: close batch item %d requires an issue ID

What it means

ValidateCloseBatchRequest rejects any batch item whose IssueID is empty, wrapping storage.ErrValidation and identifying the offending item index (0-based) in the message. Every item in a batch close must name a target issue; a blank ID can never resolve and would corrupt per-item result mapping, so it is rejected before storage work. Match with errors.Is(err, storage.ErrValidation).

Source

Thrown at internal/storage/issueops/close_batch.go:27

	"github.com/steveyegge/beads/internal/storage"
	"github.com/steveyegge/beads/internal/types"
	publicops "github.com/steveyegge/beads/issueops"
)

// ValidateCloseBatchRequest applies the request rules every BatchCloser
// implementation shares, so a rule is a contract rather than one backend's
// habit. It rejects the request outright; a per-item refusal is a result, not
// a validation failure, and never reaches here.
func ValidateCloseBatchRequest(request publicops.CloseBatchRequest) error {
	if request.Actor == "" {
		return fmt.Errorf("%w: close batch requires an actor", storage.ErrValidation)
	}
	if len(request.Items) == 0 {
		return fmt.Errorf("%w: close batch requires at least one item", storage.ErrValidation)
	}
	for i, item := range request.Items {
		if item.IssueID == "" {
			return fmt.Errorf("%w: close batch item %d requires an issue ID", storage.ErrValidation, i)
		}
	}
	if request.ClaimNext != nil {
		if err := ValidateClaimNextRequest(publicops.ClaimNextRequest{Actor: request.Actor, Filter: *request.ClaimNext}); err != nil {
			return err
		}
	}
	return nil
}

// CloseBatchCommitMessage is the history entry a batch records. It is the
// only spelling — the request carries no label to override it, and could not
// compose this one, because it names what LANDED rather than what was asked for,
// which is why it is composed from the result and not from the request: a
// batch that skipped a mistyped id must not claim it in the log.
//
// LANDED is Changed, not "no error": an idempotent re-close persisted nothing,
// so naming it would put an id in `bd dolt log` under a commit that did not

View on GitHub (pinned to 71377f2769)

Solutions

  1. Validate each item's IssueID before constructing the request and report the bad record to the user (the error message already gives the index).
  2. Check item construction/mapping code — ensure the correct ID field is copied into IssueID for every element.
  3. Match errors.Is(err, storage.ErrValidation), parse the index if needed, and drop or fix the offending item.
  4. Harden import/parsing paths to fail on rows with missing IDs at read time.

Example fix

// before
items := make([]publicops.CloseBatchItem, 0, len(rows))
for _, r := range rows {
    items = append(items, publicops.CloseBatchItem{IssueID: r.ID}) // r.ID may be ""
}
// after
for i, r := range rows {
    if r.ID == "" {
        return fmt.Errorf("row %d has no issue ID", i)
    }
    items = append(items, publicops.CloseBatchItem{IssueID: r.ID})
}
Defensive patterns

Strategy: validation

Validate before calling

for i, item := range items {
    if item.IssueID == "" {
        return fmt.Errorf("item %d is missing an issue ID", i)
    }
}

Type guard

func validBatchItems(items []publicops.CloseBatchItem) bool {
    for _, it := range items {
        if it.IssueID == "" {
            return false
        }
    }
    return true
}

Try / catch

if err := ValidateCloseBatchRequest(req); err != nil {
    if errors.Is(err, storage.ErrValidation) && strings.Contains(err.Error(), "requires an issue ID") {
        var idx int
        fmt.Sscanf(err.Error(), "close batch item %d", &idx)
        return fmt.Errorf("batch item %d has no issue ID; fix the input source", idx)
    }
    return err
}

Prevention

When it happens

Trigger: Building CloseBatchRequest items where an element has IssueID == "" — e.g. mapping over records whose ID field was never populated, or partially deserialized JSON items missing the id key.

Common situations: Parsing CLI/CSV input where one row lacks an ID; joining issue lists where the ID column name mismatched; items copied from another type with a different ID field name; truncated file imports.

Related errors


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