gastownhall/beads · error
journal: read seq counter: %w
Error message
journal: read seq counter: %w
What it means
readEventsHeadInTx reads bd_events_seq.next_seq as the head (high-water mark) of the events journal, used to compute auto-prune bounds and to page through events. This error wraps a failure of that SELECT, excluding ErrNoRows which is treated as an empty journal (returns 0, nil).
Source
Thrown at internal/storage/issueops/journal_prune.go:249
if rows[i].Seq != rows[i-1].Seq+1 {
return i
}
}
return 0
}
// readEventsHeadInTx returns the highest seq the counter has ever assigned.
// Prune never touches the counter, so this is the head of the journal's history
// even when every row has been deleted. A missing counter row means no mutation
// has ever been journaled here, which is a head of 0.
func readEventsHeadInTx(ctx context.Context, tx DBTX) (int64, error) {
var head int64
err := tx.QueryRowContext(ctx, "SELECT next_seq FROM bd_events_seq WHERE id = 0").Scan(&head)
if errors.Is(err, sql.ErrNoRows) {
return 0, nil
}
if err != nil {
return 0, fmt.Errorf("journal: read seq counter: %w", err)
}
return head, nil
}
func readEventsRowsInTx(ctx context.Context, tx DBTX, since int64, limit int) ([]storage.EventsJournalRow, error) {
// CAST(ts AS CHAR) normalizes the DATETIME to a stable string across drivers.
q := `SELECT seq, CAST(ts AS CHAR), op, issue_id, actor, issue_json, dep_json, comment_json
FROM bd_events_journal WHERE seq > ? ORDER BY seq ASC`
if limit > 0 {
q += " LIMIT " + strconv.Itoa(limit)
}
rows, err := tx.QueryContext(ctx, q, since)
if err != nil {
return nil, fmt.Errorf("journal: read since %d: %w", since, err)
}
defer rows.Close()
var out []storage.EventsJournalRowView on GitHub (pinned to 71377f2769)
Solutions
- Run migrations so bd_events_seq exists (missing table is only skipped for optional tables elsewhere, not here).
- Retry the read — transient lock waits or deadlocks on the seq row are the most common wrapped cause.
- Verify connectivity and that you are not pointing at a replica/backup missing the journal tables.
- Check the DB user has SELECT on bd_events_seq.
Example fix
// before
head, err := store.ReadEventsPageInTx(ctx, tx, since, limit) // fails against pre-migration DB
// after
if err := store.Migrate(ctx); err != nil { return err } // ensure bd_events_seq exists before journal reads
head, err := store.ReadEventsPageInTx(ctx, tx, since, limit) Defensive patterns
Strategy: try-catch
Validate before calling
var exists int
err := db.QueryRow("SELECT COUNT(*) FROM information_schema.tables WHERE table_name = 'bd_events_seq'").Scan(&exists)
if err != nil || exists == 0 {
return errors.New("run migrations: bd_events_seq missing")
} Try / catch
page, err := store.ReadEventsPageInTx(ctx, tx, since, limit)
if err != nil && strings.Contains(err.Error(), "journal: read seq counter") {
if isTransient(err) { return retryOperation() }
return fmt.Errorf("journal unreadable, check migration state: %w", err)
} Prevention
- Run migrations on startup and after version upgrades.
- Don't point the app at replicas/backup copies missing journal tables.
- Keep transactions touching bd_events_seq short.
- Grant SELECT on journal tables to the app user.
When it happens
Trigger: Calling ComputeEventsAutoPruneBoundInTx or ReadEventsPageInTx when the SELECT from bd_events_seq fails with a real driver error: lost connection, locked table, missing table without migration, or permission denial on SELECT.
Common situations: DB connection dropped between calls; a long-running writer transaction holding locks on bd_events_seq; opening a pre-migration database; read-only replica lacking the table.
Related errors
- journal: advance seq counter: %w
- journal: read seq counter: %w
- journal: read since %d: %w
- set repo mtime: %w
- clear repo mtime: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/f9d89c6b3e654855.
Report an issue: GitHub.