gastownhall/beads · error

failed to increment issue counter after seeding for prefix %

Error message

failed to increment issue counter after seeding for prefix %q: %w

What it means

After a successful seed, NextCounterIDTx re-runs the increment UPDATE for the prefix. This error wraps a failure of that second UPDATE. Because seeding verified the row exists (or inserted it), failure here usually means the transaction or connection degraded, or the seed did not persist a visible row within this transaction.

Source

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

// 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)
	}

	rowsAffected, err := res.RowsAffected()
	if err != nil {
		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)
	}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Inspect the wrapped driver error and address it directly (reconnect, free disk, clear read-only).
  2. Retry the whole operation with backoff — counter row lock contention is transient.
  3. Check whether the seed step actually inserted the row inside this transaction; fix seeding logic if the row is still missing.
  4. Serialize creation per prefix (single-flight) if lock timeouts recur under concurrency.
  5. Verify issue_counter table integrity and schema after any migration.

Example fix

// before: id, err := GenerateIssueIDInTable(ctx, tx, prefix, issue); if err != nil { return err } // no retry. // after: if err != nil && isTransientLockErr(err) { time.Sleep(50 * time.Millisecond); id, err = GenerateIssueIDInTable(ctx, tx, prefix, issue) }
Defensive patterns

Strategy: retry

Validate before calling

var n int; if err := tx.QueryRowContext(ctx, "SELECT COUNT(*) FROM issue_counter WHERE prefix = ?", prefix).Scan(&n); err != nil || n == 0 { return fmt.Errorf("counter row for prefix %q not ready", prefix) }

Type guard

func isPostSeedIncrementErr(err error) bool { return err != nil && strings.Contains(err.Error(), "failed to increment issue counter after seeding for prefix") }

Try / catch

id, err := GenerateIssueIDInTable(ctx, tx, prefix, issue); if isPostSeedIncrementErr(err) && isTransient(err) { return retryWithBackoff(ctx, 3, 50*time.Millisecond, func() error { id, err = GenerateIssueIDInTable(ctx, tx, prefix, issue); return err }) }

Prevention

When it happens

Trigger: The second `UPDATE issue_counter SET last_id = last_id + 1 WHERE prefix = ?` fails due to a connection drop mid-transaction, a lock wait timeout on the counter row (another writer grabbed it between seed and increment), a read-only database, or a broken DBTX whose ExecContext errors on the second call (faulty mocks).

Common situations: High-concurrency issue creation contending on the same prefix row; embedded Dolt server restarting mid-create; test harness DBTX stubs that fail on repeated calls; disk-full or read-only filesystem on the DB host.

Related errors


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