gastownhall/beads · error

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

Error message

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

What it means

This error wraps a failure of the atomic `UPDATE issue_counter SET last_id = last_id + 1 WHERE prefix = ?` statement executed inside nextCounterIDTx when generating a counter-mode issue ID. It indicates the SQL update itself returned a driver/database error (syntax, table missing, lock/transaction failure, connection drop), not that the row was absent. The %w wrap preserves the underlying Dolt/SQL error for diagnosis.

Source

Thrown at internal/storage/dolt/issues.go:861

			return fmt.Errorf("failed to seed issue_counter for prefix %q at %d: %w", prefix, maxNum, err)
		}
	}

	return nil
}

// nextCounterIDTx atomically increments and returns the next sequential issue ID
// for the given prefix within an existing transaction. Returns the full ID string
// (e.g., "bd-1"). Used by both generateIssueID and generateIssueIDInTable.
func nextCounterIDTx(ctx context.Context, tx *sql.Tx, prefix string) (string, error) {
	// Increment atomically at the DB level to avoid duplicate IDs under
	// concurrent transactions (GH#2002). "last_id = last_id + 1" is evaluated
	// by the DB engine atomically within Dolt's MVCC.

	// Attempt atomic increment of an existing counter row.
	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 {
		// No counter row yet - seed from existing issues before proceeding to
		// avoid collisions with manually-created sequential IDs (GH#2002).
		if seedErr := seedCounterFromExistingIssuesTx(ctx, tx, prefix); seedErr != nil {
			return "", fmt.Errorf("failed to seed issue counter for prefix %q: %w", prefix, seedErr)
		}
		// Retry the atomic increment after seeding.
		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)
		}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check the wrapped cause (%w) for the specific SQL error; if 'table issue_counter not found', run the beads schema migration to create the issue_counter table.
  2. Verify the Dolt server/storage backend is healthy: restart the embedded/remote Dolt process and retry the operation.
  3. Confirm the transaction is still open and the context was not cancelled before the UPDATE (check upstream ctx deadlines).
  4. If corruption is suspected, run bd doctor / Dolt integrity checks on the repository database.

Example fix

// before
res, err := tx.ExecContext(ctx, "UPDATE issue_counter SET last_id = last_id + 1 WHERE prefix = ?", prefix)
// after (ensure schema exists first)
if _, err := tx.ExecContext(ctx, "CREATE TABLE IF NOT EXISTS issue_counter (prefix VARCHAR(64) PRIMARY KEY, last_id BIGINT NOT NULL)"); err != nil {
    return "", fmt.Errorf("ensure issue_counter table: %w", err)
}
res, err := tx.ExecContext(ctx, "UPDATE issue_counter SET last_id = last_id + 1 WHERE prefix = ?", prefix)
Defensive patterns

Strategy: retry

Validate before calling

var n int
if err := db.QueryRow("SELECT COUNT(*) FROM information_schema.tables WHERE table_name = 'issue_counter'").Scan(&n); err != nil || n == 0 {
    return fmt.Errorf("issue_counter table missing; run schema migration")
}

Type guard

func isSQLStatementErr(err error) bool {
    var sqlErr *sqle.Error
    return errors.As(err, &sqlErr)
}

Try / catch

id, err := store.CreateIssue(ctx, issue)
if err != nil && strings.Contains(err.Error(), "failed to increment issue counter") {
    log.Printf("counter update failed: %v (underlying: %w)", err, errors.Unwrap(errors.Unwrap(err)))
    // retry once after checking DB health
}

Prevention

When it happens

Trigger: Calling generateIssueIDInTable (e.g. via issue creation with issue_id_mode=counter) when the UPDATE against the issue_counter table errors: the table does not exist in the current database version, the transaction was aborted/rolled back, the connection dropped mid-statement, or Dolt rejects the statement (lock timeout, MVCC conflict surfaced as error).

Common situations: Upgrading beads against an older .beads database whose schema lacks the issue_counter table; running with a stale or killed Dolt server process; disk full or corrupt Dolt working set causing statement failure; schema migration partially applied.

Related errors


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