{"record":{"id":"6a96ac7c97f0dc3f","repo":"gastownhall/beads","slug":"journal-raise-seq-counter-to-high-water-mark-w","errorCode":null,"errorMessage":"journal: raise seq counter to high-water mark: %w","messagePattern":"journal: raise seq counter to high-water mark: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/journal.go","lineNumber":497,"sourceCode":"\treturn nil\n}\n\n// healEventSeqCounter seeds the counter row if it is missing and raises it to\n// the journal's high-water mark, so the next allocation cannot collide. VALUES +\n// GREATEST, not INSERT ... SELECT MAX(): in Dolt a literal+aggregate SELECT over\n// an empty table yields zero rows, so an INSERT ... SELECT would seed nothing on\n// a fresh journal. GREATEST also makes this safe to call on a counter that is\n// already ahead — it never moves the counter backwards.\nfunc healEventSeqCounter(ctx context.Context, tx DBTX) error {\n\tif _, err := tx.ExecContext(ctx, \"INSERT IGNORE INTO bd_events_seq (id, next_seq) VALUES (0, 0)\"); err != nil {\n\t\treturn fmt.Errorf(\"journal: seed seq counter: %w\", err)\n\t}\n\tif _, err := tx.ExecContext(ctx, `\n\t\tUPDATE bd_events_seq\n\t\tSET next_seq = GREATEST(next_seq, COALESCE((SELECT MAX(seq) FROM bd_events_journal), 0))\n\t\tWHERE id = 0\n\t`); err != nil {\n\t\treturn fmt.Errorf(\"journal: raise seq counter to high-water mark: %w\", err)\n\t}\n\treturn nil\n}\n\n// nextEventSeq allocates the next journal sequence number from the single-row\n// bd_events_seq counter, INSIDE the caller's transaction. Incrementing the\n// shared counter row is what serializes seq assignment: two transactions that\n// both allocate a seq contend on the one row, so only one commit order survives.\n// The value becomes the journal row's seq, yielding gapless, commit-ordered seqs\n// (a rolled-back transaction rolls back its increment, burning no seq). The\n// counter persists across restart and prune never touches it, so seq never\n// resets. The seed row is created by migration 0064 / ignored 0022; the\n// self-heal below re-creates it at the journal's high-water mark if it is ever\n// missing, so a re-seed can never collide with an existing seq. A counter that\n// is PRESENT but stale cannot be detected here without a per-emit MAX(seq)\n// read, so insertEventRow heals that case reactively off the duplicate-key\n// failure instead — see there.\nfunc nextEventSeq(ctx context.Context, tx DBTX) (int64, error) {","sourceCodeStart":479,"sourceCodeEnd":515,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/journal.go#L479-L515","documentation":"healEventSeqCounter raises the singleton counter to GREATEST(next_seq, MAX(seq) in bd_events_journal); failure of that UPDATE is wrapped as 'journal: raise seq counter to high-water mark'. This keeps the seq allocator from re-issuing taken numbers; failure blocks journal writes and therefore the user's mutation.","triggerScenarios":"The GREATEST/COALESCE subquery UPDATE fails: bd_events_journal missing, connection drop mid-transaction, Dolt refusing the aggregate subquery in UPDATE (version/feature gap), or lock contention.","commonSituations":"Old Dolt driver that cannot evaluate UPDATE with correlated aggregate subquery; journal table dropped or partially restored from backup; heavy contention on the counter row across many concurrent writers.","solutions":["Update the Dolt driver/server to a version supporting aggregate subqueries in UPDATE","Run migrations/doctor to ensure bd_events_journal exists","Reduce concurrent writers or rely on the driver's row locking; retry the operation","Restore the journal table if it was partially dropped/restored"],"exampleFix":"// before\n-- partial restore left journal empty but seq ahead, ops failing oddly\nDELETE FROM bd_events_journal;\n-- after (full consistent restore, then let heal run)\nRESTORE FROM 'backup.db';\n-- then retry: healEventSeqCounter raises counter via GREATEST safely","handlingStrategy":"retry","validationCode":"_, err := db.Exec(\"SELECT COALESCE(MAX(seq),0) FROM bd_events_journal\")\nif err != nil { return fmt.Errorf(\"bd_events_journal unreadable; migrate/restore first: %w\", err) }","typeGuard":null,"tryCatchPattern":"err := doWrite(ctx, tx)\nif err != nil && strings.Contains(err.Error(), \"raise seq counter\") {\n    if isTransientSQL(err) { return retryWithBackoff(doWrite) }\n    return fmt.Errorf(\"persistent seq-heal failure, likely driver/schema: %w\", err)\n}","preventionTips":["Keep Dolt driver/server versions current (aggregate subquery in UPDATE)","Never partially restore bd_events_journal without the seq table","Retry transient SQL errors with backoff","Run bd doctor after infrastructure changes"],"tags":["journal","database","sequence"],"backgroundTag":"journal-seq-counter-unhealthy","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}