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

  1. Inspect the wrapped error (%w) for the driver's root cause
  2. Retry the operation; transient connection/context failures usually clear on retry
  3. Verify the target table exists and the transaction is still alive before ID generation
  4. 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

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


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/f029a31e241246b8. Report an issue: GitHub.