{"record":{"id":"6442e849113752c4","repo":"gastownhall/beads","slug":"journal-read-since-d-w","errorCode":null,"errorMessage":"journal: read since %d: %w","messagePattern":"journal: read since (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/journal_prune.go","lineNumber":263,"sourceCode":"\tif errors.Is(err, sql.ErrNoRows) {\n\t\treturn 0, nil\n\t}\n\tif err != nil {\n\t\treturn 0, fmt.Errorf(\"journal: read seq counter: %w\", err)\n\t}\n\treturn head, nil\n}\n\nfunc readEventsRowsInTx(ctx context.Context, tx DBTX, since int64, limit int) ([]storage.EventsJournalRow, error) {\n\t// CAST(ts AS CHAR) normalizes the DATETIME to a stable string across drivers.\n\tq := `SELECT seq, CAST(ts AS CHAR), op, issue_id, actor, issue_json, dep_json, comment_json\n\t      FROM bd_events_journal WHERE seq > ? ORDER BY seq ASC`\n\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","sourceCodeStart":245,"sourceCodeEnd":281,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/journal_prune.go#L245-L281","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nrows, err := tx.QueryContext(ctx, \"SELECT ... WHERE seq > ?\", \"abc\") // invalid since value\n// after\nsince, err := store.EventsHead(ctx) // obtain a valid numeric cursor\nrows, err := tx.QueryContext(ctx, q, since)","handlingStrategy":"retry","validationCode":"var minSeq, maxSeq int64\nif err := db.QueryRow(\"SELECT MIN(seq), MAX(seq) FROM bd_events_journal\").Scan(&minSeq, &maxSeq); err != nil {\n    return fmt.Errorf(\"journal table unreadable or missing: %w\", err)\n}\nif since < minSeq || since > maxSeq+1 { return fmt.Errorf(\"cursor %d outside journal range [%d,%d]\", since, minSeq, maxSeq) }","typeGuard":null,"tryCatchPattern":"rows, err := store.ReadEventsInTx(ctx, tx, since)\nif err != nil && strings.Contains(err.Error(), \"journal: read since\") {\n    if isTransient(err) { return retryRead(since) }\n    return fmt.Errorf(\"journal read failed at cursor %v: %w\", since, err)\n}","preventionTips":["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."],"tags":["storage","database","journal","sql"],"backgroundTag":"journal-query-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}