multica-ai/multica · warning

--months-back=%d would skip buckets before %s (oldest availa

Error message

--months-back=%d would skip buckets before %s (oldest available %s) and the watermark would still advance past them; re-run with --force-partial to accept this, or omit --months-back for a full backfill

What it means

Deliberate operator guard, not a runtime failure: --months-back=N computes a cutoff later than the oldest task_usage row, and because the run stamps the watermark at now()-5min, every bucket older than the cutoff would be permanently skipped (the cron worker never looks back that far). The command refuses to silently lose data and demands an explicit decision.

Source

Thrown at server/cmd/backfill_task_usage_hourly/main.go:140

		slog.Info("task_usage is empty; nothing to backfill")
		if *dryRun {
			return nil
		}
		return stampWatermark(ctx, pool)
	}

	from := monthFloor(minTS.Time.UTC())
	end := monthFloor(maxTS.Time.UTC()).AddDate(0, 1, 0)

	if *monthsBack > 0 {
		cutoff := monthFloor(time.Now().UTC()).AddDate(0, -(*monthsBack), 0)
		// A partial backfill still stamps the watermark at now()-5min, so
		// buckets older than the cutoff are abandoned permanently: the cron
		// worker will never look back that far. That data loss must be an
		// explicit operator decision — require --force-partial to proceed.
		if cutoff.After(from) {
			if !*forcePartial {
				return fmt.Errorf("--months-back=%d would skip buckets before %s (oldest available %s) and the watermark would still advance past them; re-run with --force-partial to accept this, or omit --months-back for a full backfill",
					*monthsBack, cutoff.Format(time.RFC3339), minTS.Time.UTC().Format(time.RFC3339))
			}
			from = cutoff
			slog.Warn("partial backfill: --months-back limits coverage; older buckets will be left empty and the watermark will still advance past them",
				"months_back", *monthsBack, "effective_from", from.Format(time.RFC3339),
				"oldest_available", minTS.Time.UTC().Format(time.RFC3339))
		}
	}

	slog.Info("backfill range", "from", from.Format(time.RFC3339), "to", end.Format(time.RFC3339), "dry_run", *dryRun, "sleep_between_slices", sleep.String())

	cursor := from
	var totalRows int64
	for cursor.Before(end) {
		next := cursor.AddDate(0, 1, 0)
		if *dryRun {
			slog.Info("would roll up slice", "from", cursor.Format(time.RFC3339), "to", next.Format(time.RFC3339))
			cursor = next

View on GitHub (pinned to 2c0912b6ec)

Solutions

  1. Omit --months-back to run the full backfill from the oldest row (recommended if the data matters)
  2. Add --force-partial only if you explicitly accept permanent gaps in buckets older than the cutoff
  3. If unsure, run with --dry-run first to see the range and coverage

Example fix

# before
backfill_task_usage_hourly -months-back=3
# -> error: would skip buckets ...

# after (full, lossless)
backfill_task_usage_hourly

# after (explicit partial, data loss accepted)
backfill_task_usage_hourly -months-back=3 -force-partial
Defensive patterns

Strategy: validation

Validate before calling

if *monthsBack > 0 && !*forcePartial {
    cutoff := monthFloor(time.Now().UTC()).AddDate(0, -*monthsBack, 0)
    if cutoff.After(from) { /* refuse or require --force-partial */ }
}

Prevention

When it happens

Trigger: Running with --months-back smaller than the data's age span, e.g. 6 months of task_usage exists and --months-back=3 without --force-partial.

Common situations: Operator wants a 'quick recent backfill' not realizing the watermark advance makes the skipped history unrecoverable via normal operation.

Related errors


AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15). Data as JSON: /api/errors/627384f812e1d3fe. Report an issue: GitHub.