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
This is an exhaustive-failure error from GenerateIssueIDInTable: after probing every length in the range (baseLength–maxLength) with 10 nonce variations each, every candidate ID already existed in the table. It is not a driver failure — the queries succeeded but every generated hash ID collided, which is astronomically unlikely unless something is systematically wrong.
Source
Thrown at internal/storage/issueops/helpers.go:216
}
for length := baseLength; length <= maxLength; length++ {
for nonce := 0; nonce < 10; nonce++ {
candidate := idgen.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)
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)
}
// IsCounterModeTx checks whether issue_id_mode=counter is configured.
func IsCounterModeTx(ctx context.Context, tx DBTX) (bool, error) {
var idMode string
err := tx.QueryRowContext(ctx, "SELECT value FROM config WHERE `key` = ?", "issue_id_mode").Scan(&idMode)
if err != nil && err != sql.ErrNoRows {
return false, fmt.Errorf("failed to read issue_id_mode config: %w", err)
}
return idMode == "counter", nil
}
// NextCounterIDTx atomically increments and returns the next sequential issue ID.
func NextCounterIDTx(ctx context.Context, tx DBTX, prefix string) (string, error) {
res, err := tx.ExecContext(ctx, "UPDATE issue_counter SET last_id = last_id + 1 WHERE prefix = ?", prefix)
if err != nil {
return "", fmt.Errorf("failed to increment issue counter for prefix %q: %w", prefix, err)
}View on GitHub (pinned to 71377f2769)
Solutions
- Check for duplicate creation: the issue you're trying to create probably already exists — look it up instead of inserting again
- Ensure CreatedAt is a fresh timestamp per issue (check for frozen clocks or reused time values in import/retry code)
- Change the title/description or actor slightly, or regenerate with a different CreatedAt to alter the hash input
- Switch to counter mode (issue_id_mode=counter) which uses sequential IDs instead of hash probing
Example fix
// before issue.CreatedAt = lastKnownTime // reused across retries id, err := GenerateIssueIDInTable(ctx, tx, issue, actor) // after issue.CreatedAt = time.Now().UTC() // fresh timestamp per creation attempt id, err := GenerateIssueIDInTable(ctx, tx, issue, actor)
Defensive patterns
Strategy: validation
Validate before calling
// check whether the issue already exists before attempting generation
existing, err := GetIssueInTx(ctx, tx, candidatePrefixID)
if err == nil && existing != nil && existing.Title == issue.Title {
return existing, nil // idempotent re-create
} Try / catch
id, err := GenerateIssueIDInTable(ctx, tx, issue, actor)
if err != nil {
if strings.Contains(err.Error(), "failed to generate unique ID") {
// exhaustion: check for duplicate create, or fall back to counter mode
return detectAndReturnExistingIssue(ctx, tx, issue)
}
return err
} Prevention
- Never reuse a stale CreatedAt value across create retries — always use a fresh time.Now()
- Deduplicate imports: look up issues by content before creating
- Use counter mode (issue_id_mode=counter) for high-volume or deterministic-clock environments
- Keep the ID length range reasonably wide (default baseLength–maxLength) rather than pinning a short length
When it happens
Trigger: Calling GenerateIssueIDInTable when the same (prefix, title, description, actor, createdAt) tuple produces identical inputs and the DB contains the resulting IDs at every length/nonce combination — e.g. repeated bulk imports replaying the same issues, or clock skew/freezing causing identical CreatedAt values across many creates.
Common situations: Importing the same JSONL export twice; a retry loop recreating issues with identical title/description/actor/timestamp; tampered or stubbed clock in tests generating the same CreatedAt; extremely short configured ID lengths.
Related errors
- failed to generate unique ID for prefix %q after lengths %d.
- failed to generate unique ID after trying lengths %d-%d with
- db: NextCounterID: prefix must not be empty
- generating Linear milestone epic ID: %w
- artifact collision for %s: %s already exists with different
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/90817526b925405f.
Report an issue: GitHub.