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 seeding, the code retries the same atomic UPDATE on issue_counter. This error means the retry UPDATE statement itself failed with a SQL/driver error. Note that if seeding inserted the counter row correctly, the retry should affect a row; an error here is a statement-level failure (lock, abort, connection, missing table).
Source
Thrown at internal/storage/dolt/issues.go:878
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)
}
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 {View on GitHub (pinned to 71377f2769)
Solutions
- Simply retry the issue-creation command; MVCC/lock conflicts are often transient.
- Reduce concurrent writers to the same .beads database (or use bd's built-in locking) and retry.
- Check the wrapped cause: if it is a lock timeout, increase the engine's lock/transaction timeout or shorten the surrounding transaction.
- If persistent, verify the issue_counter row was actually written by seeding (SELECT last_id FROM issue_counter WHERE prefix=...).
Example fix
// before
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)
}
// after (bounded retry for transient conflicts)
for attempt := 0; attempt < 3; attempt++ {
res, err = tx.ExecContext(ctx, "UPDATE issue_counter SET last_id = last_id + 1 WHERE prefix = ?", prefix)
if err == nil {
break
}
if !isTransientSQLErr(err) {
return "", fmt.Errorf("failed to increment issue counter after seeding for prefix %q: %w", prefix, err)
}
time.Sleep(50 * time.Millisecond << attempt)
} Defensive patterns
Strategy: retry
Validate before calling
// before heavy concurrent creation, check for counter contention
var writers int
_ = db.QueryRow("SELECT COUNT(*) FROM information_schema.processlist WHERE info LIKE '%issue_counter%'").Scan(&writers)
if writers > 1 { time.Sleep(time.Second) } Type guard
func isTransientSQLErr(err error) bool {
msg := err.Error()
return strings.Contains(msg, "lock") || strings.Contains(msg, "conflict") || strings.Contains(msg, "timeout")
} Try / catch
var id string
var err error
for i := 0; i < 3; i++ {
id, err = store.CreateIssue(ctx, issue)
if err == nil || !strings.Contains(err.Error(), "increment issue counter after seeding") {
break
}
time.Sleep(100 * time.Millisecond << i)
} Prevention
- Avoid multiple concurrent bd processes writing to the same embedded database
- Batch issue creation in one process instead of parallel invocations
- Keep transactions around counter updates short
- Upgrade to a beads version with bounded retries on counter conflicts
When it happens
Trigger: generateIssueIDInTable in counter mode where the initial UPDATE affected 0 rows, seeding succeeded, but the retried `UPDATE issue_counter SET last_id = last_id + 1` returns an error — e.g. transaction conflict/serialization failure under concurrency, or the seed ran on a tx already marked bad.
Common situations: Two concurrent bd processes creating issues on the same database and racing on the counter row; Dolt MVCC conflict reported as an SQL error; long-running transaction hitting a lock timeout.
Related errors
- failed to read issue counter after increment for prefix %q:
- get next child ID: update counter: %w
- failed to check issue_counter for prefix %q: %w
- failed to query existing issues for prefix %q: %w
- failed to scan issue id: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/d2eae7a494b7387b.
Report an issue: GitHub.