{"record":{"id":"fd8eeb8d7505c489","repo":"gastownhall/beads","slug":"journal-scan-row-w","errorCode":null,"errorMessage":"journal: scan row: %w","messagePattern":"journal: scan row: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/journal_prune.go","lineNumber":276,"sourceCode":"\tif limit > 0 {\n\t\tq += \" LIMIT \" + strconv.Itoa(limit)\n\t}\n\trows, err := tx.QueryContext(ctx, q, since)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"journal: read since %d: %w\", since, err)\n\t}\n\tdefer rows.Close()\n\n\tvar out []storage.EventsJournalRow\n\tfor rows.Next() {\n\t\tvar (\n\t\t\tr         storage.EventsJournalRow\n\t\t\tissueJS   sql.NullString\n\t\t\tdepJS     sql.NullString\n\t\t\tcommentJS sql.NullString\n\t\t)\n\t\tif err := rows.Scan(&r.Seq, &r.TS, &r.Op, &r.IssueID, &r.Actor, &issueJS, &depJS, &commentJS); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"journal: scan row: %w\", err)\n\t\t}\n\t\tr.TS = normalizeEventsTimestamp(r.TS)\n\t\tr.IssueJSON = issueJS.String\n\t\tr.DepJSON = depJS.String\n\t\tr.CommentJSON = commentJS.String\n\t\tout = append(out, r)\n\t}\n\treturn out, rows.Err()\n}\n\n// normalizeEventsTimestamp emits the journal boundary's stable RFC3339Nano UTC\n// contract. Dolt/MySQL stringify DATETIME with a space rather than a `T`, and a\n// driver may or may not append an offset; a consumer parsing the record needs\n// one parsable UTC timestamp regardless of which shape the backend produced.\nfunc normalizeEventsTimestamp(raw string) string {\n\tfor _, layout := range []string{\n\t\ttime.RFC3339Nano,\n\t\t\"2006-01-02 15:04:05.999999999Z07:00\",","sourceCodeStart":258,"sourceCodeEnd":294,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/journal_prune.go#L258-L294","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nALTER TABLE bd_events_journal DROP COLUMN actor; -- schema drift\n// after\nALTER TABLE bd_events_journal ADD COLUMN actor VARCHAR(255) NOT NULL DEFAULT ''; -- restore expected column","handlingStrategy":"validation","validationCode":"cols, _ := db.Query(\"SHOW COLUMNS FROM bd_events_journal\")\nwant := map[string]bool{\"seq\": true, \"ts\": true, \"op\": true, \"issue_id\": true, \"actor\": true, \"issue_json\": true, \"dep_json\": true, \"comment_json\": true}\nfor 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) }\nif len(want) > 0 { return fmt.Errorf(\"schema drift: missing columns %v\", want) }","typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"journal: scan row\") {\n    return fmt.Errorf(\"bd_events_journal schema drifted; run migrations or restore matching schema: %w\", err)\n}","preventionTips":["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."],"tags":["storage","database","journal","schema-mismatch"],"backgroundTag":"row-scan-type-mismatch","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}