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 notView on GitHub (pinned to 71377f2769)
Solutions
- Validate each item's IssueID before constructing the request and report the bad record to the user (the error message already gives the index).
- Check item construction/mapping code — ensure the correct ID field is copied into IssueID for every element.
- Match errors.Is(err, storage.ErrValidation), parse the index if needed, and drop or fix the offending item.
- 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
- Validate every item at construction time, not just at the storage boundary.
- Fail fast in parsers/imports when a row lacks an ID.
- Use a helper to build CloseBatchItem that rejects empty IDs.
- Map the correct source ID field when converting between types.
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
- %w: close batch requires an actor
- %w: close batch requires at least one item
- proxy.ForceStopUnverified: at most one options value is allo
- Linear project milestone is missing id
- proxy.ForceStopUnverified: timeout must be positive, got %s
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/72392feccd8c5ea9.
Report an issue: GitHub.