gastownhall/beads · error · issueops.ErrValidation
%w: apply batch item %d: %s names both key %q and id %q; exa
Error message
%w: apply batch item %d: %s names both key %q and id %q; exactly one
What it means
validateApplyRef enforces that each batch ref names an issue by exactly one identifier. This error fires when both ref.Key and ref.ID are set, which would be ambiguous — the applier cannot know which identifier is authoritative.
Source
Thrown at internal/storage/batch_apply.go:326
return nil
}
declaredAt, ok := keyIndex[ref.Key]
if !ok {
return &issueops.RefError{Index: index, Member: member, Key: ref.Key}
}
if declaredAt >= index {
return &issueops.RefError{Index: index, Member: member, Key: ref.Key, DeclaredLater: true}
}
return nil
}
// validateApplyRef checks the exactly-one rule every ref answers to.
func validateApplyRef(ref issueops.Ref, index int, member string) error {
switch {
case ref.Key == "" && ref.ID == "":
return fmt.Errorf("%w: apply batch item %d: %s must name a key or an id", issueops.ErrValidation, index, member)
case ref.Key != "" && ref.ID != "":
return fmt.Errorf("%w: apply batch item %d: %s names both key %q and id %q; exactly one",
issueops.ErrValidation, index, member, ref.Key, ref.ID)
}
return nil
}
// applyTouchKeyRef renders a ref as the address the touched set is keyed by.
// The two namespaces are kept apart so a key and an id that happen to read the
// same string are not confused for one row.
func applyTouchKeyRef(ref issueops.Ref) string {
if ref.Key != "" {
return "key:" + ref.Key
}
return "id:" + ref.ID
}
// applyRefLabel renders a ref for a message.
func applyRefLabel(ref issueops.Ref) string {
if ref.Key != "" {View on GitHub (pinned to 71377f2769)
Solutions
- Clear one of the two fields so exactly one identifier remains (prefer Key for readability, ID for stability)
- Add client-side normalization that strips ID when Key is present (or vice versa)
- Fix the code that populates the Ref to set only one field
Example fix
// before
ref := issueops.Ref{Key: "beads-42", ID: "bd-a1b2c3"}
// after
ref := issueops.Ref{Key: "beads-42"} Defensive patterns
Strategy: validation
Validate before calling
func exactlyOne(r issueops.Ref) error {
if r.Key != "" && r.ID != "" {
return fmt.Errorf("ref sets both key %q and id %q", r.Key, r.ID)
}
return nil
} Type guard
func refUnambiguous(r issueops.Ref) bool { return (r.Key == "") != (r.ID == "") } Prevention
- Normalize refs at construction: prefer Key, clear ID
- Never copy full issue structs into Ref fields
- Add a unit test for ref construction helpers
When it happens
Trigger: Passing a Ref with both Key and ID non-empty to a batch apply item (create path or target ref validation).
Common situations: Copying a fully-populated issue struct into a Ref instead of picking one field; merging payloads where one source supplied key and another supplied id; ORM lookups that fill both fields.
Related errors
- %w: apply batch item %d is kind %q but carries another kind'
- %w: apply batch item %d requires an issue
- %w: apply batch item %d must not carry comments or dependenc
- %w: apply batch item %d reuses key %q, already declared by i
- %w: apply batch item %d has a metadata_ref with an empty key
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/4d13a54698193eab.
Report an issue: GitHub.