gastownhall/beads · error
failed to generate unique ID after trying lengths %d-%d with
Error message
failed to generate unique ID after trying lengths %d-%d with 10 nonces each
What it means
Exhaustion error from generateIssueIDInTable: after trying every candidate length from baseLength to maxLength with 10 nonces each, every generated hash ID already existed in the table. This means the ID space for the given prefix/title inputs is saturated for this issue's content, an extraordinarily rare probabilistic event unless inputs are degenerate (e.g., many issues with identical title/description/actor/timestamp).
Source
Thrown at internal/storage/dolt/wisps.go:85
}
for length := baseLength; length <= maxLength; length++ {
for nonce := 0; nonce < 10; nonce++ {
candidate := generateHashID(prefix, issue.Title, issue.Description, actor, issue.CreatedAt, length, nonce)
var count int
err = tx.QueryRowContext(ctx, fmt.Sprintf(`SELECT COUNT(*) FROM %s WHERE id = ?`, table), candidate).Scan(&count) //nolint:gosec // G201
if err != nil {
return "", fmt.Errorf("failed to check for ID collision: %w", err)
}
if count == 0 {
return candidate, nil
}
}
}
return "", fmt.Errorf("failed to generate unique ID after trying lengths %d-%d with 10 nonces each", baseLength, maxLength)
}
// getAdaptiveIDLengthFromTable returns the adaptive ID length based on table size.
//
//nolint:gosec // G201: table is a hardcoded constant
func getAdaptiveIDLengthFromTable(ctx context.Context, tx *sql.Tx, table, prefix string) int {
var count int
if err := tx.QueryRowContext(ctx, fmt.Sprintf(`SELECT COUNT(*) FROM %s WHERE id LIKE ?`, table), prefix+"%").Scan(&count); err != nil {
return 4 // Default for wisps (small tables)
}
switch {
case count < 100:
return 4
case count < 1000:
return 5
case count < 10000:
return 6View on GitHub (pinned to 71377f2769)
Solutions
- Retry creation — with distinct timestamps or a nonce-advancing retry a free ID is almost always found
- For bulk imports, vary CreatedAt/title or stagger inserts so hash inputs differ
- Check whether a bug is producing constant hash inputs (same actor + frozen clock)
- If the table is genuinely enormous, report/raise the adaptive ID length ceiling
Defensive patterns
Strategy: retry
Validate before calling
// avoid degenerate inputs: ensure distinct CreatedAt per issue in bulk imports
if issue.CreatedAt.IsZero() || lastCreatedAt.Equal(issue.CreatedAt) && lastTitle == issue.Title {
issue.CreatedAt = issue.CreatedAt.Add(time.Second)
} Try / catch
id, err := store.CreateIssue(ctx, issue)
if err != nil && strings.Contains(err.Error(), "failed to generate unique ID") {
// perturb inputs and retry once
issue.CreatedAt = issue.CreatedAt.Add(time.Second)
id, err = store.CreateIssue(ctx, issue)
} Prevention
- Stagger CreatedAt/titles in bulk import scripts so hash inputs differ
- Never freeze timestamps across large batched creates
- Monitor table size; very large databases narrow the ID space
- If it reproduces with normal data, report as a bug in hash input construction
When it happens
Trigger: Creating an issue when all hash-ID candidates across all supported lengths and 10 nonces per length collide with existing rows; practically only with huge tables, or batch creation of issues with identical content and CreatedAt.
Common situations: Import scripts creating thousands of near-identical issues with the same timestamp/actor; a clock froze so CreatedAt is constant across a bulk import; extremely large databases shrinking the effective ID space.
Related errors
- db: NextCounterID: prefix must not be empty
- failed to check for ID collision: %w
- ErrTransaction
- ErrQuery
- ErrScan
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/cd3f98fa5874885b.
Report an issue: GitHub.