gastownhall/beads · error
failed to seed issue_counter for prefix %q at %d: %w
Error message
failed to seed issue_counter for prefix %q at %d: %w
What it means
Once the highest numeric suffix (maxNum) is found, the seeder inserts INSERT INTO issue_counter (prefix, last_id) to initialize the counter; failure of this insert is wrapped with the prefix and value. ID generation cannot proceed until the counter row exists.
Source
Thrown at internal/storage/dolt/issues.go:843
var num int
if _, parseErr := fmt.Sscanf(suffix, "%d", &num); parseErr == nil && fmt.Sprintf("%d", num) == suffix {
if num > maxNum {
maxNum = num
}
}
}
if err := rows.Err(); err != nil {
return fmt.Errorf("failed to iterate existing issues for prefix %q: %w", prefix, err)
}
// Only insert a seed row if we found at least one numeric ID.
// If no numeric IDs exist, the counter will naturally start at 1 on first use.
if maxNum > 0 {
_, err = tx.ExecContext(ctx,
"INSERT INTO issue_counter (prefix, last_id) VALUES (?, ?)",
prefix, maxNum)
if err != nil {
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)View on GitHub (pinned to 71377f2769)
Solutions
- Read the wrapped cause; retry the create — if another process seeded successfully, the SELECT will now find the row
- Verify the issue_counter table exists with the expected (prefix, last_id) schema
- Avoid concurrent initializations of a brand-new database; run one create first, then start parallel workers
- Check disk space / read-only filesystem if inserts consistently fail
Defensive patterns
Strategy: retry
Validate before calling
// Ensure counter table exists and schema matches
const cols = await describe("issue_counter"); // expect: prefix, last_id
if (!cols.includes("last_id")) throw new Error("issue_counter schema mismatch; run migrations"); Try / catch
try {
await bd.create(title);
} catch (e) {
if (String(e.message).includes("failed to seed issue_counter")) {
// likely concurrent seeder or schema issue; retry create
await backoff();
} else throw e;
} Prevention
- Don't run parallel creates against a brand-new database; create once, then parallelize
- Run migrations so issue_counter has the expected (prefix, last_id) schema
- Ensure the storage backend is writable (disk space, permissions)
- Retry on insert conflicts — an existing row means another seeder succeeded
When it happens
Trigger: First create for a prefix where the counter INSERT fails — constraint violation from a concurrent seeder, schema mismatch, transaction failure, or backend unavailability.
Common situations: Two processes seeding the same prefix concurrently (race on the insert); databases missing the issue_counter table; read-only or full storage backend.
Related errors
- failed to check issue_counter for prefix %q: %w
- failed to insert initial issue counter for prefix %q: %w
- add label %s/%s: %w
- ErrExec
- database not available: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/447d60fbabaa42bb.
Report an issue: GitHub.