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.String

View on GitHub (pinned to 71377f2769)

Solutions

  1. Retry the read; contention with concurrent DELETE (prune) is transient.
  2. Run migrations to ensure bd_events_journal exists with the expected schema.
  3. Inspect rows around the failing `since` value for corrupt seq/ts data and repair or re-export the journal.
  4. 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

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


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/6442e849113752c4. Report an issue: GitHub.