{"record":{"id":"f9d89c6b3e654855","repo":"gastownhall/beads","slug":"journal-read-seq-counter-w-f9d89c","errorCode":null,"errorMessage":"journal: read seq counter: %w","messagePattern":"journal: read seq counter: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/journal_prune.go","lineNumber":249,"sourceCode":"\t\tif rows[i].Seq != rows[i-1].Seq+1 {\n\t\t\treturn i\n\t\t}\n\t}\n\treturn 0\n}\n\n// readEventsHeadInTx returns the highest seq the counter has ever assigned.\n// Prune never touches the counter, so this is the head of the journal's history\n// even when every row has been deleted. A missing counter row means no mutation\n// has ever been journaled here, which is a head of 0.\nfunc readEventsHeadInTx(ctx context.Context, tx DBTX) (int64, error) {\n\tvar head int64\n\terr := tx.QueryRowContext(ctx, \"SELECT next_seq FROM bd_events_seq WHERE id = 0\").Scan(&head)\n\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","sourceCodeStart":231,"sourceCodeEnd":267,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/journal_prune.go#L231-L267","documentation":"readEventsHeadInTx reads bd_events_seq.next_seq as the head (high-water mark) of the events journal, used to compute auto-prune bounds and to page through events. This error wraps a failure of that SELECT, excluding ErrNoRows which is treated as an empty journal (returns 0, nil).","triggerScenarios":"Calling ComputeEventsAutoPruneBoundInTx or ReadEventsPageInTx when the SELECT from bd_events_seq fails with a real driver error: lost connection, locked table, missing table without migration, or permission denial on SELECT.","commonSituations":"DB connection dropped between calls; a long-running writer transaction holding locks on bd_events_seq; opening a pre-migration database; read-only replica lacking the table.","solutions":["Run migrations so bd_events_seq exists (missing table is only skipped for optional tables elsewhere, not here).","Retry the read — transient lock waits or deadlocks on the seq row are the most common wrapped cause.","Verify connectivity and that you are not pointing at a replica/backup missing the journal tables.","Check the DB user has SELECT on bd_events_seq."],"exampleFix":"// before\nhead, err := store.ReadEventsPageInTx(ctx, tx, since, limit) // fails against pre-migration DB\n// after\nif err := store.Migrate(ctx); err != nil { return err } // ensure bd_events_seq exists before journal reads\nhead, err := store.ReadEventsPageInTx(ctx, tx, since, limit)","handlingStrategy":"try-catch","validationCode":"var exists int\nerr := db.QueryRow(\"SELECT COUNT(*) FROM information_schema.tables WHERE table_name = 'bd_events_seq'\").Scan(&exists)\nif err != nil || exists == 0 {\n    return errors.New(\"run migrations: bd_events_seq missing\")\n}","typeGuard":null,"tryCatchPattern":"page, err := store.ReadEventsPageInTx(ctx, tx, since, limit)\nif err != nil && strings.Contains(err.Error(), \"journal: read seq counter\") {\n    if isTransient(err) { return retryOperation() }\n    return fmt.Errorf(\"journal unreadable, check migration state: %w\", err)\n}","preventionTips":["Run migrations on startup and after version upgrades.","Don't point the app at replicas/backup copies missing journal tables.","Keep transactions touching bd_events_seq short.","Grant SELECT on journal tables to the app user."],"tags":["storage","database","journal","sql"],"backgroundTag":"sequence-counter-read-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}