gastownhall/beads · error
failed to read issue counter after increment for prefix %q:
Error message
failed to read issue counter after increment for prefix %q: %w
What it means
After incrementing (or seeding+incrementing) the issue_counter row, NextCounterIDTx re-reads last_id with SELECT ... WHERE prefix = ? and the scan failed. Since a row must exist by this point (increment matched or fallback insert ran), this usually means the row vanished inside the transaction, the tx was rolled back, or a DB-level error occurred.
Source
Thrown at internal/storage/issueops/helpers.go:264
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)
}
return fmt.Sprintf("%s-%d", prefix, nextID), nil
}
// SeedCounterFromExistingIssuesTx scans existing issues to find the highest numeric suffix
// for the given prefix, then seeds the issue_counter table if no row exists yet.
func SeedCounterFromExistingIssuesTx(ctx context.Context, tx DBTX, prefix string) error {
var existing int
err := tx.QueryRowContext(ctx, "SELECT last_id FROM issue_counter WHERE prefix = ?", prefix).Scan(&existing)
if err == nil {
return nil // already seeded
}
if err != sql.ErrNoRows {
return fmt.Errorf("failed to check existing counter for prefix %q: %w", prefix, err)
}
// Find max numeric suffix among existing issues
rows, err := tx.QueryContext(ctx, `SELECT id FROM issues WHERE id LIKE CONCAT(?, '-%')`, prefix)View on GitHub (pinned to 71377f2769)
Solutions
- Check the wrapped error: if sql.ErrNoRows, verify the counter row still exists for the exact prefix (SELECT * FROM issue_counter WHERE prefix='<P>').
- Ensure a single writer path: avoid running multiple bd processes against the same DB concurrently; use bd's locking or serialize ID generation.
- Retry the ID generation inside a fresh transaction; counter increment + read is meant to be atomic within one tx.
- Confirm the prefix passed to GenerateIssueIDInTable matches the stored prefix exactly (case, trailing dash — ReadConfigPrefix trims trailing '-').
Example fix
// before
id, err := storage.GenerateIssueIDInTable(ctx, tx, "issues", prefix, issue) // fails when counter row missing
// after: ensure counter row is seeded before generating
_, err = tx.ExecContext(ctx, "INSERT IGNORE INTO issue_counter (prefix, last_id) VALUES (?, 0)", prefix)
if err != nil { return err }
id, err = storage.GenerateIssueIDInTable(ctx, tx, "issues", 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 {
if _, err := tx.ExecContext(ctx, "INSERT IGNORE INTO issue_counter (prefix, last_id) VALUES (?, 0)", prefix); err != nil {
return err
}
} Try / catch
id, err := storage.GenerateIssueIDInTable(ctx, tx, "issues", prefix, issue)
if err != nil && strings.Contains(err.Error(), "failed to read issue counter after increment") {
// retry once in a fresh transaction; counter ops must be atomic
tx2, _ := db.BeginTx(ctx, nil)
id, err = storage.GenerateIssueIDInTable(ctx, tx2, "issues", prefix, issue)
} Prevention
- Keep a single writer per database; don't run parallel bd processes on the same .beads DB.
- Ensure the prefix string is normalized identically everywhere (no trailing dash; ReadConfigPrefix trims it).
- Keep transactions short so they don't time out between increment and read.
When it happens
Trigger: GenerateIssueIDInTable in counter mode: the post-increment SELECT last_id FROM issue_counter WHERE prefix = ? returns sql.ErrNoRows or a driver error — e.g. concurrent transaction deleted the row, prefix mismatch after mutation, or connection failure during the query.
Common situations: Concurrent `bd` processes deleting/resetting counter rows; transaction timeout under Dolt under load; prefix containing characters that get normalized differently between the UPDATE and the SELECT; transient DB disconnection.
Related errors
- ErrTransaction
- open unit of work: %w
- failed to begin transaction: %w
- failed to commit is_blocked repairs: %w
- failed to begin transaction: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/5493e50f5d4c6e83.
Report an issue: GitHub.