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
- Inspect the wrapped driver error and address it directly (reconnect, free disk, clear read-only).
- Retry the whole operation with backoff — counter row lock contention is transient.
- Check whether the seed step actually inserted the row inside this transaction; fix seeding logic if the row is still missing.
- Serialize creation per prefix (single-flight) if lock timeouts recur under concurrency.
- 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
- Retry transient lock-timeout and deadlock errors with exponential backoff.
- Keep transactions short around counter updates to reduce lock contention.
- Ensure sufficient disk space and a writable DB directory.
- After migrations, sanity-check issue_counter schema and row state per prefix.
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
- failed to increment issue counter for prefix %q: %w
- failed to begin transaction: %w
- failed to commit is_blocked repairs: %w
- failed to increment issue counter for prefix %q: %w
- db: ChildCounterSQLRepository.NextChildID: read counter for
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/fa4ac60059ff7c81.
Report an issue: GitHub.