gastownhall/beads · error

journal: prune below %d: %w

Error message

journal: prune below %d: %w

What it means

PruneEventsInTx executes DELETE FROM bd_events_journal WHERE <computed bound> to drop journal rows below the computed seq. This error wraps the DELETE failing, and includes the `before` seq bound in the message. The prune itself was correctly computed (skip was false); only the deletion failed.

Source

Thrown at internal/storage/issueops/journal_prune.go:422

	}
	return readRowsCeil, readDaysFloor
}

// PruneEventsInTx deletes journal rows with seq below before, honoring the
// retain-days and retain-rows floors (0 disables a floor), and returns the
// number of rows deleted. It runs inside the caller's transaction.
func PruneEventsInTx(ctx context.Context, tx DBTX, before int64, retainDays, retainRows int, now time.Time) (int64, error) {
	readRowsCeil, readDaysFloor := eventsPruneFloorReadersInTx(ctx, tx, retainRows)
	where, args, skip, err := ComputeEventsPruneWhere(before, retainDays, retainRows, now, readRowsCeil, readDaysFloor)
	if err != nil {
		return 0, err
	}
	if skip {
		return 0, nil
	}
	res, err := tx.ExecContext(ctx, "DELETE FROM bd_events_journal WHERE "+where, args...)
	if err != nil {
		return 0, fmt.Errorf("journal: prune below %d: %w", before, err)
	}
	return res.RowsAffected()
}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Retry the prune — lock contention with readers is the most common cause and is transient.
  2. Ensure the DB user has DELETE privilege and the connection is not read-only.
  3. Prune in smaller batches (lower retention steps) so each DELETE stays under timeout limits.
  4. Schedule pruning outside peak write windows to reduce lock waits.

Example fix

// before
defer store.PruneEventsInTx(ctx, tx, bound) // giant single DELETE, times out
// after
for bound > 0 { // prune incrementally
  step := bound; if step > 10000 { step = 10000 }
  if _, err := store.PruneEventsInTx(ctx, tx, step); err != nil { return err }
  bound -= step
}
Defensive patterns

Strategy: retry

Validate before calling

var ro int
_ = db.QueryRow("SELECT @@read_only").Scan(&ro)
if ro == 1 { return errors.New("database is read-only; pruning will fail") }

Try / catch

n, err := store.PruneEventsInTx(ctx, tx, before)
if err != nil && strings.Contains(err.Error(), "journal: prune below") {
    if isLockWaitOrTransient(err) { return retryPrune(before) }
    return fmt.Errorf("prune to %d failed; check DELETE grants and FK/trigger config: %w", before, err)
}

Prevention

When it happens

Trigger: PruneEventsInTx failing at ExecContext: foreign-key or trigger on bd_events_journal rejecting deletes, lock contention with concurrent journal readers/writers, read-only connection, lost connection mid-DELETE, or a corrupted `where` clause from a malformed bound.

Common situations: Long-running DELETE timing out under lock contention on an active journal; running with a read-only DB user; server-enforced max execution time exceeded on very large deletes.

Related errors


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