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 over

View on GitHub (pinned to 71377f2769)

Solutions

  1. Read the wrapped %w error for the SQL root cause
  2. Run bd doctor / migrations to ensure bd_events_journal exists
  3. Check DB permissions and disk space
  4. Retry the operation if the cause was transient (connection drop)
  5. 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

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


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/ecc96f0db9d10c1f. Report an issue: GitHub.