gastownhall/beads · error

failed to scan existing issues for prefix %q: %w

Error message

failed to scan existing issues for prefix %q: %w

What it means

The query 'SELECT id FROM issues WHERE id LIKE CONCAT(?, '-%')' used to discover the highest existing issue number for a prefix failed at execution time. The wrapper is purely contextual; the wrapped driver error carries the actual cause.

Source

Thrown at internal/storage/issueops/helpers.go:284

	return fmt.Sprintf("%s-%d", prefix, nextID), nil
}

// SeedCounterFromExistingIssuesTx scans existing issues to find the highest numeric suffix
// for the given prefix, then seeds the issue_counter table if no row exists yet.
func SeedCounterFromExistingIssuesTx(ctx context.Context, tx DBTX, prefix string) error {
	var existing int
	err := tx.QueryRowContext(ctx, "SELECT last_id FROM issue_counter WHERE prefix = ?", prefix).Scan(&existing)
	if err == nil {
		return nil // already seeded
	}
	if err != sql.ErrNoRows {
		return fmt.Errorf("failed to check existing counter for prefix %q: %w", prefix, err)
	}

	// Find max numeric suffix among existing issues
	rows, err := tx.QueryContext(ctx, `SELECT id FROM issues WHERE id LIKE CONCAT(?, '-%')`, prefix)
	if err != nil {
		return fmt.Errorf("failed to scan existing issues for prefix %q: %w", prefix, err)
	}
	defer rows.Close()

	maxNum := 0
	pfxDash := prefix + "-"
	for rows.Next() {
		var id string
		if err := rows.Scan(&id); err != nil {
			continue
		}
		suffix := strings.TrimPrefix(id, pfxDash)
		if strings.Contains(suffix, ".") {
			continue // skip child IDs
		}
		if n, err := strconv.Atoi(suffix); err == nil && n > maxNum {
			maxNum = n
		}
	}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Read the wrapped error: if the tx was invalidated by an earlier failure, restart the whole operation in a new transaction.
  2. Run schema migrations to ensure the issues table exists with expected columns.
  3. If the driver lacks CONCAT support (non-Dolt SQLite backends), ensure you are using the correct storage driver for your database.
  4. Retry on transient connection errors; check DB connectivity logs.

Example fix

// before: reusing a failed tx
if err != nil { return err } // earlier failure left tx dead
rows, err := tx.QueryContext(...) // 'failed to scan existing issues'
// after: abort and retry the entire unit in a fresh transaction
if err != nil { return retryWithNewTx(op) }
Defensive patterns

Strategy: retry

Validate before calling

if _, err := tx.QueryContext(ctx, "SELECT 1 FROM issues LIMIT 1"); err != nil {
    return fmt.Errorf("issues table unavailable: %w", err)
}

Try / catch

if err != nil && strings.Contains(err.Error(), "failed to scan existing issues") {
    // abandon the poisoned tx and retry in a fresh one
    tx.Rollback()
    return retryWithFreshTx(ctx, op)
}

Prevention

When it happens

Trigger: SeedCounterFromExistingIssuesTx (via NextCounterIDTx) on an unseeded prefix: tx.QueryContext on the issues table fails — table missing, CONCAT/LIKE incompatibility with the driver, connection error, or transaction already aborted by a prior error.

Common situations: Schema drift (issues table renamed/absent); using a driver that doesn't support CONCAT in LIKE patterns; issuing the call on a transaction that earlier failed and was invalidated.

Understand the failure class

Background: "query failed", "%w: SQL error" — wrapped database query errors in Go libraries explained — this error's family across 3 libraries.

Related errors


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