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 the counter, the code reads back `SELECT last_id FROM issue_counter WHERE prefix = ?` to form the new issue ID. This error wraps failure of that read: most notably sql.ErrNoRows when the counter row unexpectedly vanished (missing INSERT path, concurrent delete), or a scan/connection error. Without this value the new ID cannot be produced, so ID generation aborts.
Source
Thrown at internal/storage/dolt/issues.go:897
}
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 {
return false, fmt.Errorf("failed to read issue_id_mode config: %w", err)
}
return idMode == "counter", nil
}
// generateHashID creates a hash-based ID for a top-level issue.
// Uses base36 encoding (0-9, a-z) for better information density than hex.
func generateHashID(prefix, title, description, creator string, timestamp time.Time, length, nonce int) string {
return idgen.GenerateHashID(prefix, title, description, creator, timestamp, length, nonce)View on GitHub (pinned to 71377f2769)
Solutions
- Check the wrapped cause: if ErrNoRows, verify another process isn't deleting/resetting issue_counter and that you're on the expected database/branch.
- Retry the ID generation; a transient connection drop will usually succeed on retry.
- Confirm the UPDATE path actually located the row (re-run with rowsAffected handling fixed) so the SELECT finds it.
- Inspect issue_counter contents directly (SELECT * FROM issue_counter WHERE prefix='<p>') to see if the row exists.
Example fix
// before
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)
}
// after
err = tx.QueryRowContext(ctx, "SELECT last_id FROM issue_counter WHERE prefix = ?", prefix).Scan(&nextID)
if err == sql.ErrNoRows {
return "", fmt.Errorf("issue counter row missing for prefix %q after increment; check concurrent writers/branch", prefix)
} else if err != nil {
return "", fmt.Errorf("failed to read issue counter after increment for prefix %q: %w", prefix, err)
} Defensive patterns
Strategy: retry
Validate before calling
var n int
if err := db.QueryRow("SELECT COUNT(*) FROM issue_counter WHERE prefix = ?", prefix).Scan(&n); err != nil || n == 0 {
return fmt.Errorf("counter row for %q missing; concurrent writers or wrong branch", prefix)
} Type guard
func isCounterRowMissing(err error) bool {
return errors.Is(err, sql.ErrNoRows) || strings.Contains(err.Error(), "read issue counter after increment")
} Try / catch
id, err := store.CreateIssue(ctx, issue)
if err != nil && strings.Contains(err.Error(), "read issue counter after increment") {
if isCounterRowMissing(err) {
return fmt.Errorf("counter row vanished; check concurrent writers and current branch: %w", err)
}
// transient read failure: retry once
id, err = store.CreateIssue(ctx, issue)
} Prevention
- Never run maintenance (branch reset, counter deletes) while issues are being created
- Confirm all writers target the same Dolt branch/database
- Use one writer process for embedded databases
- Retry transient failures; investigate repeats via SELECT on issue_counter
When it happens
Trigger: Counter-mode ID generation where the increment UPDATE ran (or was skipped via a broken rowsAffected path) but the SELECT returns no row or errors — concurrent transaction deleted/never created the row, wrong database/branch context, or connection failure during query.
Common situations: Concurrent writers where one process rolls back the counter row; operating against a different Dolt branch/database than the one the counter was written to; a connection dropped between UPDATE and SELECT.
Related errors
- failed to increment issue counter after seeding for prefix %
- 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/faf0d12ade4d93dc.
Report an issue: GitHub.