gastownhall/beads · error
journal: read since %d: %w
Error message
journal: read since %d: %w
What it means
readEventsRowsInTx executes the journal page query (SELECT ... FROM bd_events_journal WHERE seq > ? ORDER BY seq ASC ... LIMIT n) and wraps any QueryContext failure with this message, including the requested `since` value for diagnostics. It means the journal rows themselves could not be fetched, after the head read succeeded.
Source
Thrown at internal/storage/issueops/journal_prune.go:263
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.EventsJournalRow
for rows.Next() {
var (
r storage.EventsJournalRow
issueJS sql.NullString
depJS sql.NullString
commentJS sql.NullString
)
if err := rows.Scan(&r.Seq, &r.TS, &r.Op, &r.IssueID, &r.Actor, &issueJS, &depJS, &commentJS); err != nil {
return nil, fmt.Errorf("journal: scan row: %w", err)
}
r.TS = normalizeEventsTimestamp(r.TS)
r.IssueJSON = issueJS.String
r.DepJSON = depJS.String
r.CommentJSON = commentJS.StringView on GitHub (pinned to 71377f2769)
Solutions
- Retry the read; contention with concurrent DELETE (prune) is transient.
- Run migrations to ensure bd_events_journal exists with the expected schema.
- Inspect rows around the failing `since` value for corrupt seq/ts data and repair or re-export the journal.
- Serialize prune and read operations, or chunk reads, if lock contention recurs.
Example fix
// before rows, err := tx.QueryContext(ctx, "SELECT ... WHERE seq > ?", "abc") // invalid since value // after since, err := store.EventsHead(ctx) // obtain a valid numeric cursor rows, err := tx.QueryContext(ctx, q, since)
Defensive patterns
Strategy: retry
Validate before calling
var minSeq, maxSeq int64
if err := db.QueryRow("SELECT MIN(seq), MAX(seq) FROM bd_events_journal").Scan(&minSeq, &maxSeq); err != nil {
return fmt.Errorf("journal table unreadable or missing: %w", err)
}
if since < minSeq || since > maxSeq+1 { return fmt.Errorf("cursor %d outside journal range [%d,%d]", since, minSeq, maxSeq) } Try / catch
rows, err := store.ReadEventsInTx(ctx, tx, since)
if err != nil && strings.Contains(err.Error(), "journal: read since") {
if isTransient(err) { return retryRead(since) }
return fmt.Errorf("journal read failed at cursor %v: %w", since, err)
} Prevention
- Persist and reuse valid numeric cursors instead of hand-made ones.
- Avoid running full-journal reads concurrently with pruning.
- Keep bd_events_journal schema in sync with app version via migrations.
- Chunk large reads to reduce lock hold time.
When it happens
Trigger: ReadEventsInTx / ReadEventsPageInTx failing at QueryContext: the WHERE seq > ? comparison fails due to a corrupt or malformed seq/ts column (the code CASTs ts AS CHAR to normalize), connection loss, table missing, or lock contention with a pruning DELETE.
Common situations: Pruning job deleting rows while a reader pages the journal (lock contention on Dolt/MySQL); corrupted seq values from manual edits; pre-migration DB missing bd_events_journal.
Related errors
- journal: advance seq counter: %w
- journal: read seq counter: %w
- journal: read seq counter: %w
- set repo mtime: %w
- clear repo mtime: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/6442e849113752c4.
Report an issue: GitHub.