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

When seeding found no existing numeric issue IDs, the code inserts the very first counter row `INSERT INTO issue_counter (prefix, last_id) VALUES (?, 1)`. This error wraps failure of that INSERT: primary-key conflict (another writer inserted the row in the meantime), schema/table missing, constraint violation, or transaction abort.

Source

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

		// 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)
		}
		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 {
			// Seeding found no existing numeric IDs -- insert the initial row.
			_, 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)
			}
		}
	}

	// Read back the value that was atomically set by the DB engine.
	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
}

// isCounterModeTx checks whether issue_id_mode=counter is configured.
func isCounterModeTx(ctx context.Context, tx *sql.Tx) (bool, error) {
	var idMode string
	err := tx.QueryRowContext(ctx, "SELECT value FROM config WHERE `key` = ?", "issue_id_mode").Scan(&idMode)
	if err != nil && err != sql.ErrNoRows {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check the wrapped cause: if it is a duplicate-key error, the row now exists — just retry the operation (the counter is already initialized).
  2. Run the schema migration to ensure issue_counter exists, then retry.
  3. Serialize repository initialization: initialize once with a single bd process before running parallel commands.
  4. Check storage writability (disk space, read-only mount, server permissions) if the insert persists in failing.

Example fix

// before
_, 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)
}
// after (tolerate a concurrent initializer)
if _, err = tx.ExecContext(ctx, "INSERT IGNORE INTO issue_counter (prefix, last_id) VALUES (?, 1)", prefix); err != nil {
    return "", fmt.Errorf("failed to insert initial issue counter for prefix %q: %w", prefix, err)
}
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("run schema migration before first counter-mode write")
}

Type guard

func isDuplicateKeyErr(err error) bool {
    return err != nil && (strings.Contains(err.Error(), "duplicate") || strings.Contains(err.Error(), "Duplicate entry"))
}

Try / catch

id, err := store.CreateIssue(ctx, issue)
if err != nil && strings.Contains(err.Error(), "insert initial issue counter") {
    if isDuplicateKeyErr(err) {
        // another process initialized the counter; safe to retry
        id, err = store.CreateIssue(ctx, issue)
    }
}

Prevention

When it happens

Trigger: First-ever counter-mode ID creation for a prefix in an empty database, where the initial INSERT fails — most commonly because a concurrent creator already inserted the same prefix row (duplicate-key error), or the issue_counter table doesn't exist.

Common situations: Two bd processes initializing a fresh repository simultaneously; a repository created by a newer beads version against an older schema without issue_counter; read-only or full-disk storage backends rejecting the write.

Related errors


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