gastownhall/beads · error
journal: raise seq counter to high-water mark: %w
Error message
journal: raise seq counter to high-water mark: %w
What it means
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.
Source
Thrown at internal/storage/issueops/journal.go:497
return nil
}
// healEventSeqCounter seeds the counter row if it is missing and raises it to
// the journal's high-water mark, so the next allocation cannot collide. VALUES +
// GREATEST, not INSERT ... SELECT MAX(): in Dolt a literal+aggregate SELECT over
// an empty table yields zero rows, so an INSERT ... SELECT would seed nothing on
// a fresh journal. GREATEST also makes this safe to call on a counter that is
// already ahead — it never moves the counter backwards.
func healEventSeqCounter(ctx context.Context, tx DBTX) error {
if _, err := tx.ExecContext(ctx, "INSERT IGNORE INTO bd_events_seq (id, next_seq) VALUES (0, 0)"); err != nil {
return fmt.Errorf("journal: seed seq counter: %w", err)
}
if _, err := tx.ExecContext(ctx, `
UPDATE bd_events_seq
SET next_seq = GREATEST(next_seq, COALESCE((SELECT MAX(seq) FROM bd_events_journal), 0))
WHERE id = 0
`); err != nil {
return fmt.Errorf("journal: raise seq counter to high-water mark: %w", err)
}
return nil
}
// nextEventSeq allocates the next journal sequence number from the single-row
// bd_events_seq counter, INSIDE the caller's transaction. Incrementing the
// shared counter row is what serializes seq assignment: two transactions that
// both allocate a seq contend on the one row, so only one commit order survives.
// The value becomes the journal row's seq, yielding gapless, commit-ordered seqs
// (a rolled-back transaction rolls back its increment, burning no seq). The
// counter persists across restart and prune never touches it, so seq never
// resets. The seed row is created by migration 0064 / ignored 0022; the
// self-heal below re-creates it at the journal's high-water mark if it is ever
// missing, so a re-seed can never collide with an existing seq. A counter that
// is PRESENT but stale cannot be detected here without a per-emit MAX(seq)
// read, so insertEventRow heals that case reactively off the duplicate-key
// failure instead — see there.
func nextEventSeq(ctx context.Context, tx DBTX) (int64, error) {View on GitHub (pinned to 71377f2769)
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
Example fix
// before -- partial restore left journal empty but seq ahead, ops failing oddly DELETE FROM bd_events_journal; -- after (full consistent restore, then let heal run) RESTORE FROM 'backup.db'; -- then retry: healEventSeqCounter raises counter via GREATEST safely
Defensive patterns
Strategy: retry
Validate before calling
_, err := db.Exec("SELECT COALESCE(MAX(seq),0) FROM bd_events_journal")
if err != nil { return fmt.Errorf("bd_events_journal unreadable; migrate/restore first: %w", err) } Try / catch
err := doWrite(ctx, tx)
if err != nil && strings.Contains(err.Error(), "raise seq counter") {
if isTransientSQL(err) { return retryWithBackoff(doWrite) }
return fmt.Errorf("persistent seq-heal failure, likely driver/schema: %w", err)
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- journal: seed seq counter: %w
- journal dependency removals for source-repo delete: %w
- capture dependency edges for rename %s -> %s: %w
- journal: snapshot comment for %s: %w
- journal: read is_blocked for %s: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/6a96ac7c97f0dc3f.
Report an issue: GitHub.