gastownhall/beads · error

failed to insert initial issue counter for prefix %q: %w

Error message

failed to insert initial issue counter for prefix %q: %w

What it means

NextCounterIDTx failed to insert the first row (last_id=1) into the issue_counter table for a prefix. This fallback runs only after the UPDATE increment matched 0 rows and seeding from existing issues also left no row (e.g. no existing issues with that prefix). The wrapped SQL error is the real cause (table missing, constraint violation, permissions, DB connection).

Source

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

		return "", fmt.Errorf("failed to check rows affected for issue counter prefix %q: %w", prefix, err)
	}

	if rowsAffected == 0 {
		if seedErr := SeedCounterFromExistingIssuesTx(ctx, tx, prefix); seedErr != nil {
			return "", fmt.Errorf("failed to seed issue counter for prefix %q: %w", prefix, seedErr)
		}
		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 after seeding for prefix %q: %w", prefix, err)
		}
		rowsAffected, err = res.RowsAffected()
		if err != nil {
			return "", fmt.Errorf("failed to check rows affected after seeding for prefix %q: %w", prefix, err)
		}
		if rowsAffected == 0 {
			_, err = tx.ExecContext(ctx, "INSERT INTO issue_counter (prefix, last_id) VALUES (?, 1)", prefix)
			if err != nil {
				return "", fmt.Errorf("failed to insert initial issue counter for prefix %q: %w", prefix, err)
			}
		}
	}

	var nextID int
	err = tx.QueryRowContext(ctx, "SELECT last_id FROM issue_counter WHERE prefix = ?", prefix).Scan(&nextID)
	if err != nil {
		return "", fmt.Errorf("failed to read issue counter after increment for prefix %q: %w", prefix, err)
	}
	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 {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Run schema migrations so the issue_counter table exists (bd should apply migrations on open; verify with a SELECT 1 FROM issue_counter query).
  2. Pre-seed the counter manually: INSERT INTO issue_counter (prefix, last_id) VALUES ('<PREFIX>', 0), then retry ID generation.
  3. Read the wrapped %w error: if it is 'no such table', apply migrations; if it is a connection/lock error, retry the operation.
  4. Check DB connectivity and that the transaction is still valid (not rolled back/timed out) before calling again.

Example fix

// before: counter mode on an unmigrated DB fails with 'no such table: issue_counter'
// after
// apply migrations at startup before generating IDs
db.SetDefaultMigrationTimeout(30 * time.Second)
if err := storage.Migrate(ctx, db); err != nil { log.Fatal(err) }
id, err := storage.GenerateIssueIDInTable(ctx, tx, "issues", "PROJ", issue)
Defensive patterns

Strategy: retry

Validate before calling

var exists int
if err := tx.QueryRowContext(ctx, "SELECT COUNT(*) FROM issue_counter").Scan(&exists); err != nil {
    return fmt.Errorf("issue_counter table missing; run migrations first: %w", err)
}

Try / catch

id, err := storage.GenerateIssueIDInTable(ctx, tx, "issues", prefix, issue)
if err != nil {
    var inner error
    if errors.As(err, &inner) && strings.Contains(inner.Error(), "no such table") {
        if mErr := storage.Migrate(ctx, db); mErr != nil { log.Fatal(mErr) }
        id, err = storage.GenerateIssueIDInTable(ctx, tx, "issues", prefix, issue)
    }
}

Prevention

When it happens

Trigger: Calling GenerateIssueIDInTable (counter mode) with a prefix that has no issue_counter row and no existing issues: the INSERT INTO issue_counter (prefix, last_id) VALUES (?, 1) fails, typically because the issue_counter table does not exist (old schema, not migrated) or the tx/DB is broken.

Common situations: Dolt database created before the counter-mode schema migration; counter mode enabled via issue_id_mode=counter but migrations not applied; a custom prefix used for the first issue ever; read-only or crashed connection mid-transaction.

Related errors


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