gastownhall/beads · error
journal: scan row: %w
Error message
journal: scan row: %w
What it means
While iterating journal rows, rows.Scan into EventsJournalRow (Seq, TS, Op, IssueID, Actor, and three nullable JSON columns) failed. This wraps column-count or type-conversion mismatches between the live table schema and what the scanner expects. TS is normalized afterwards, and NULL JSON columns are collapsed to empty strings.
Source
Thrown at internal/storage/issueops/journal_prune.go:276
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
out = append(out, r)
}
return out, rows.Err()
}
// normalizeEventsTimestamp emits the journal boundary's stable RFC3339Nano UTC
// contract. Dolt/MySQL stringify DATETIME with a space rather than a `T`, and a
// driver may or may not append an offset; a consumer parsing the record needs
// one parsable UTC timestamp regardless of which shape the backend produced.
func normalizeEventsTimestamp(raw string) string {
for _, layout := range []string{
time.RFC3339Nano,
"2006-01-02 15:04:05.999999999Z07:00",View on GitHub (pinned to 71377f2769)
Solutions
- Run `bd doctor` / migrations to reconcile bd_events_journal schema with the expected projection.
- Compare the table's columns against the SELECT in readEventsRowsInTx; fix drift with an explicit ALTER or restore from a matching-version backup.
- Find the offending row (scan page-by-page around the failure) and NULL/repair out-of-range seq or op values.
- Upgrade/downgrade the Dolt/MySQL driver to a version matching the app's expectations.
Example fix
// before ALTER TABLE bd_events_journal DROP COLUMN actor; -- schema drift // after ALTER TABLE bd_events_journal ADD COLUMN actor VARCHAR(255) NOT NULL DEFAULT ''; -- restore expected column
Defensive patterns
Strategy: validation
Validate before calling
cols, _ := db.Query("SHOW COLUMNS FROM bd_events_journal")
want := map[string]bool{"seq": true, "ts": true, "op": true, "issue_id": true, "actor": true, "issue_json": true, "dep_json": true, "comment_json": true}
for cols.Next() { var c string; var t, null, key, extra interface{}; _ = cols.Scan(&c, &t, &null, &key, &extra); if !want[c] { return fmt.Errorf("schema drift: unexpected column %s", c) }; delete(want, c) }
if len(want) > 0 { return fmt.Errorf("schema drift: missing columns %v", want) } Try / catch
if err != nil && strings.Contains(err.Error(), "journal: scan row") {
return fmt.Errorf("bd_events_journal schema drifted; run migrations or restore matching schema: %w", err)
} Prevention
- Never hand-ALTER journal tables; let migrations own the schema.
- Restore backups only from matching app versions.
- Run `bd doctor` after restores to detect schema drift early.
- Pin the Dolt/SQL driver version in go.mod.
When it happens
Trigger: ReadEventsInTx / ReadEventsPageInTx hitting a row whose shape differs from the SELECT projection: schema drift (extra/missing/reordered columns after a manual ALTER or partial migration), a non-integer or NULL value in a NOT NULL-expected column (seq, op), or a driver returning an unexpected type for ts.
Common situations: Manually altered bd_events_journal; restoring a table from an older export with fewer columns; a driver version returning DATETIME/timestamp in a type the scanner rejects.
Related errors
- journal: scan dependency removal from %s: %w
- journal: advance seq counter: %w
- journal: read seq counter: %w
- journal: read seq counter: %w
- journal: read since %d: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/fd8eeb8d7505c489.
Report an issue: GitHub.