gastownhall/beads · error
journal: record %s for %s: %w
Error message
journal: record %s for %s: %w
What it means
The journal INSERT failed with a non-duplicate-key error; insertEventRow wraps it as 'journal: record <op> for <id>'. Duplicate-key errors are specially handled by healing the seq counter, so anything reaching this wrap is a genuine SQL failure (constraint violation, connection loss, permissions, table missing).
Source
Thrown at internal/storage/issueops/journal.go:466
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`, seq, time.Now().UTC(), string(op), issueID, actor, issueJSON, depJSON, commentJSON)
return err
}
seq, err := nextEventSeq(ctx, tx)
if err != nil {
return err
}
if err := insert(seq); err != nil {
// A duplicate seq means the counter is BEHIND the journal — it was
// restored, hand-edited, or copied from another workspace. Left alone
// that wedges the instance permanently: every later mutation re-mints
// the same taken seq and fails, and because the journal row shares the
// mutation's transaction, the user's write fails with it. Raise the
// counter past the high-water mark and retry exactly once; a second
// duplicate is a real bug and must surface, not spin.
if !dberrors.IsDuplicateKey(err) {
return fmt.Errorf("journal: record %s for %s: %w", op, issueID, err)
}
if healErr := healEventSeqCounter(ctx, tx); healErr != nil {
return healErr
}
seq, err = nextEventSeq(ctx, tx)
if err != nil {
return err
}
if err := insert(seq); err != nil {
return fmt.Errorf("journal: record %s for %s after seq counter heal: %w", op, issueID, err)
}
}
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 overView on GitHub (pinned to 71377f2769)
Solutions
- Read the wrapped %w error for the SQL root cause
- Run bd doctor / migrations to ensure bd_events_journal exists
- Check DB permissions and disk space
- Retry the operation if the cause was transient (connection drop)
- If it persists after a seq-heal retry, it indicates real corruption — restore from backup
Defensive patterns
Strategy: retry
Validate before calling
// ensure journal schema exists before writes
if err := migrate.Run(ctx, db); err != nil { return err }
_ = db.QueryRow("SELECT 1 FROM bd_events_journal LIMIT 1") Try / catch
err := ops.RecordEventInTx(ctx, tx, op, id, nil, nil)
if err != nil {
if !errors.Is(err, storage.ErrNotFound) && isTransientSQL(err) {
return retryWithBackoff(func() error { return doWrite(ctx, tx2, op, id) })
}
return err
} Prevention
- Keep schema migrated (bd_events_journal present)
- Monitor disk space and DB permissions
- Use connection retry policy for Dolt/driver drops
- Classify errors: only non-duplicate, transient errors warrant retry
When it happens
Trigger: INSERT INTO bd_events_journal fails for reasons other than duplicate seq: connection dropped mid-transaction, bd_events_journal missing, CHECK/FK constraint violated, disk full, Dolt driver error.
Common situations: Schema not migrated (journal table absent); database read-only or permission revoked; Dolt server restarted mid-write; disk quota exceeded on the host.
Related errors
- 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
- journal: seed seq counter: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/ecc96f0db9d10c1f.
Report an issue: GitHub.