gastownhall/beads · error

journal: compute retain-days floor: %w

Error message

journal: compute retain-days floor: %w

What it means

In the retain-days pruning strategy, the cutoff time is converted to a floor seq via EventsPruneDaysFloorQuery (MIN(seq) over rows older than cutoff). This error wraps a Scan failure of that query. ErrNoRows and a NULL MIN (no matching rows) are both treated as 'no bound', so this is always a real driver error.

Source

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

		var ceil int64
		scanErr := tx.QueryRowContext(ctx, EventsPruneRowsCeilQuery(), retainRows).Scan(&ceil)
		if errors.Is(scanErr, sql.ErrNoRows) {
			return 0, false, nil
		}
		if scanErr != nil {
			return 0, false, fmt.Errorf("journal: compute retain-rows floor: %w", scanErr)
		}
		return ceil, true, nil
	}
	readDaysFloor := func(cutoff time.Time) (int64, bool, error) {
		// MIN over no matching rows yields one NULL row, not ErrNoRows.
		var floorSeq sql.NullInt64
		scanErr := tx.QueryRowContext(ctx, EventsPruneDaysFloorQuery(), cutoff).Scan(&floorSeq)
		if errors.Is(scanErr, sql.ErrNoRows) {
			return 0, false, nil
		}
		if scanErr != nil {
			return 0, false, fmt.Errorf("journal: compute retain-days floor: %w", scanErr)
		}
		if !floorSeq.Valid {
			return 0, false, nil
		}
		return floorSeq.Int64, true, nil
	}
	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
	}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Retry; transient connection or lock errors are common wrapped causes.
  2. Verify migrations created bd_events_journal with a normalized ts column (CAST(ts AS CHAR) normalization exists for driver variance).
  3. Check the Dolt/driver version supports the date comparison used by EventsPruneDaysFloorQuery; upgrade if not.
  4. Inspect server logs for the exact underlying SQL error.

Example fix

// before
store.ComputeEventsAutoPruneBoundInTx(ctx, tx, PruneOpts{RetainDays: 0}) // nonsensical cutoff
// after
store.ComputeEventsAutoPruneBoundInTx(ctx, tx, PruneOpts{RetainDays: 30}) // sane retention window
Defensive patterns

Strategy: validation

Validate before calling

if opts.RetainDays <= 0 { return errors.New("RetainDays must be positive") }
if _, err := db.Query("SELECT MIN(seq) FROM bd_events_journal WHERE ts < NOW()"); err != nil {
    return fmt.Errorf("day-floor query unsupported on this driver: %w", err)
}

Try / catch

if err != nil && strings.Contains(err.Error(), "journal: compute retain-days floor") {
    if isDialectError(err) { return fallbackToRetainRowsPolicy(opts) }
    return err
}

Prevention

When it happens

Trigger: ComputeEventsAutoPruneBoundInTx with a RetainDays policy failing at the floor query: type mismatch comparing the ts column against the cutoff parameter, missing table, connection failure, or driver incompatibility with the date comparison expression.

Common situations: Driver/dialect mismatch where the cutoff time cannot be bound or compared (e.g. older Dolt not supporting the DATETIME comparison form); clock/cutoff values far outside stored range triggering edge-case casts; pre-migration DB.

Related errors


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