gastownhall/beads · error · ErrAmbiguousID
%w: %q matches %d issues: %v Use more characters to disambig
Error message
%w: %q matches %d issues: %v Use more characters to disambiguate
What it means
The partial ID matched more than one issue, so ResolvePartialID refuses to guess and returns an error wrapping ErrAmbiguousID (detectable with errors.Is). The message lists every matching ID (sorted deterministically) and asks for a longer fragment. This is by design to prevent operating on the wrong issue.
Source
Thrown at internal/utils/id_parser.go:224
}
}
if exactMatch != "" {
return exactMatch, nil
}
}
}
if len(matches) == 0 {
return "", fmt.Errorf("no issue found matching %q", input)
}
// Sort so the ambiguity error lists IDs deterministically. SearchIssues return
// order is not a contract for ambiguous matches, so sorting by ID pins the same
// message for every storage implementation.
sort.Strings(matches)
if len(matches) > 1 {
return "", fmt.Errorf("%w: %q matches %d issues: %v\nUse more characters to disambiguate", ErrAmbiguousID, input, len(matches), matches)
}
return matches[0], nil
}
func partialIDSearchPart(hashPart string) (string, bool) {
if !looksLikePartialIDHash(hashPart) {
return "", false
}
searchPart := hashPart
if idx := strings.LastIndex(hashPart, "-"); idx >= 0 && idx < len(hashPart)-1 {
suffix := hashPart[idx+1:]
if looksLikePartialIDHash(suffix) {
searchPart = suffix
}
}
return searchPart, true
}View on GitHub (pinned to 71377f2769)
Solutions
- Use more characters of the hash until exactly one match remains (as the error suggests)
- Use the full ID copied from `bd show` or `bd list` output
- Filter first with `bd search <keywords>` to narrow to one issue, then use its ID
- In code, test with errors.Is(err, utils.ErrAmbiguousID) and prompt the user to choose from the listed matches
Example fix
// before
id, err := utils.ResolvePartialID(ctx, store, "a3", cfg) // ambiguous
// after
id, err := utils.ResolvePartialID(ctx, store, "a3f8e9", cfg) // longer fragment
if errors.Is(err, utils.ErrAmbiguousID) {
return fmt.Errorf("pick one: %v", err)
} Defensive patterns
Strategy: type-guard
Validate before calling
// Pre-check uniqueness before resolving:
ids, err := store.SearchIssueIDs(ctx, hashFragment, types.IssueFilter{})
if err == nil && len(ids) > 1 {
return fmt.Errorf("fragment %q is ambiguous: %v", hashFragment, ids)
} Type guard
func isAmbiguousID(err error) bool {
return errors.Is(err, utils.ErrAmbiguousID)
} Try / catch
id, err := utils.ResolvePartialID(ctx, store, frag, cfg)
if isAmbiguousID(err) {
// parse the listed matches and ask the user to pick
return interactivePick(err.Error())
}
if err != nil { return err } Prevention
- Use at least 6-8 hash characters in scripts
- Pass full IDs when automating — never 1-2 char fragments
- Check errors.Is(err, ErrAmbiguousID) and surface the candidate list to users
- Prefer `bd search <keywords>` to narrow to one issue before ID resolution
When it happens
Trigger: Calling ResolvePartialID with a hash fragment that is a leading prefix of two or more issue hashes — e.g. "a3" when both "bd-a3f8e9" and "bd-a3f11" exist. Only leading-prefix matches count (HasPrefix), so interior substrings do not trigger this.
Common situations: Using 1-2 character abbreviations in scripts on databases with thousands of issues; copy-pasting only the first few characters of a hash; a freshly created issue whose hash happens to share a prefix with an existing one.
Related errors
- ErrAmbiguousID
- resolving molecule ID %s: %w
- searching closed gates: %w
- epic '%s' not found: %v
- issue '%s' not found: %v
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/2f212732b2a530f3.
Report an issue: GitHub.