gastownhall/beads · error
failed to check for ID collision: %w
Error message
failed to check for ID collision: %w
What it means
generateIssueIDInTable probes candidate hash IDs for collisions with SELECT COUNT(*) queries; if one of those collision-check queries fails, this error wraps the cause. It means the ID-generation loop could not even determine whether a candidate ID is free, so ID creation aborts. It is a database-level failure, not an actual ID collision.
Source
Thrown at internal/storage/dolt/wisps.go:76
}
}
baseLength := getAdaptiveIDLengthFromTable(ctx, tx, table, prefix)
var err error
maxLength := 8
if baseLength > maxLength {
baseLength = maxLength
}
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)View on GitHub (pinned to 71377f2769)
Solutions
- Inspect the wrapped error (%w) for the driver's root cause
- Retry the operation; transient connection/context failures usually clear on retry
- Verify the target table exists and the transaction is still alive before ID generation
- Increase the context timeout if generation runs under heavy load with many collisions
Defensive patterns
Strategy: retry
Validate before calling
// preflight: confirm table reachable and tx viable
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
var one int
if err := db.QueryRowContext(ctx, `SELECT 1`).Scan(&one); err != nil {
return fmt.Errorf("database unavailable: %w", err)
} Try / catch
id, err := store.CreateIssue(ctx, issue)
if err != nil && strings.Contains(err.Error(), "failed to check for ID collision") {
// transient DB failure during generation — retry with backoff
time.Sleep(500 * time.Millisecond)
id, err = store.CreateIssue(ctx, issue)
} Prevention
- Pass contexts with generous timeouts into create paths
- Retry create operations on transient wrapped driver errors
- Ensure schema migrations have created all issue/wisp tables before writes
- Avoid sharing one aborted transaction across many generation probes
When it happens
Trigger: tx.QueryRowContext(...SELECT COUNT(*) FROM <table> WHERE id = ?...) returns an error: the transaction was aborted/rolled back by a prior error, the table does not exist, connection dropped mid-transaction, or a context deadline fires during the loop.
Common situations: Long ID-generation loops exceeding a context timeout under load; dropped DB connections; schema drift where the wisps/issues table is missing; transaction killed by a concurrent Dolt conflict.
Related errors
- ErrTransaction
- db: NextCounterID: prefix must not be empty
- open unit of work: %w
- failed to begin transaction: %w
- failed to commit is_blocked repairs: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/f029a31e241246b8.
Report an issue: GitHub.