{"record":{"id":"627384f812e1d3fe","repo":"multica-ai/multica","slug":"months-back-d-would-skip-buckets-before-s-old","errorCode":null,"errorMessage":"--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","messagePattern":"--months-back=(.+?) would skip buckets before (.+?) \\(oldest available (.+?)\\) and the watermark would still advance past them; re-run with --force-partial to accept this, or omit --months-back for a full backfill","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"server/cmd/backfill_task_usage_hourly/main.go","lineNumber":140,"sourceCode":"\t\tslog.Info(\"task_usage is empty; nothing to backfill\")\n\t\tif *dryRun {\n\t\t\treturn nil\n\t\t}\n\t\treturn stampWatermark(ctx, pool)\n\t}\n\n\tfrom := monthFloor(minTS.Time.UTC())\n\tend := monthFloor(maxTS.Time.UTC()).AddDate(0, 1, 0)\n\n\tif *monthsBack > 0 {\n\t\tcutoff := monthFloor(time.Now().UTC()).AddDate(0, -(*monthsBack), 0)\n\t\t// A partial backfill still stamps the watermark at now()-5min, so\n\t\t// buckets older than the cutoff are abandoned permanently: the cron\n\t\t// worker will never look back that far. That data loss must be an\n\t\t// explicit operator decision — require --force-partial to proceed.\n\t\tif cutoff.After(from) {\n\t\t\tif !*forcePartial {\n\t\t\t\treturn 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\",\n\t\t\t\t\t*monthsBack, cutoff.Format(time.RFC3339), minTS.Time.UTC().Format(time.RFC3339))\n\t\t\t}\n\t\t\tfrom = cutoff\n\t\t\tslog.Warn(\"partial backfill: --months-back limits coverage; older buckets will be left empty and the watermark will still advance past them\",\n\t\t\t\t\"months_back\", *monthsBack, \"effective_from\", from.Format(time.RFC3339),\n\t\t\t\t\"oldest_available\", minTS.Time.UTC().Format(time.RFC3339))\n\t\t}\n\t}\n\n\tslog.Info(\"backfill range\", \"from\", from.Format(time.RFC3339), \"to\", end.Format(time.RFC3339), \"dry_run\", *dryRun, \"sleep_between_slices\", sleep.String())\n\n\tcursor := from\n\tvar totalRows int64\n\tfor cursor.Before(end) {\n\t\tnext := cursor.AddDate(0, 1, 0)\n\t\tif *dryRun {\n\t\t\tslog.Info(\"would roll up slice\", \"from\", cursor.Format(time.RFC3339), \"to\", next.Format(time.RFC3339))\n\t\t\tcursor = next","sourceCodeStart":122,"sourceCodeEnd":158,"githubUrl":"https://github.com/multica-ai/multica/blob/2c0912b6ec764b373d44eeea1e80f0d9f11ab417/server/cmd/backfill_task_usage_hourly/main.go#L122-L158","documentation":"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.","triggerScenarios":"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.","commonSituations":"Operator wants a 'quick recent backfill' not realizing the watermark advance makes the skipped history unrecoverable via normal operation.","solutions":["Omit --months-back to run the full backfill from the oldest row (recommended if the data matters)","Add --force-partial only if you explicitly accept permanent gaps in buckets older than the cutoff","If unsure, run with --dry-run first to see the range and coverage"],"exampleFix":"# before\nbackfill_task_usage_hourly -months-back=3\n# -> error: would skip buckets ...\n\n# after (full, lossless)\nbackfill_task_usage_hourly\n\n# after (explicit partial, data loss accepted)\nbackfill_task_usage_hourly -months-back=3 -force-partial","handlingStrategy":"validation","validationCode":"if *monthsBack > 0 && !*forcePartial {\n    cutoff := monthFloor(time.Now().UTC()).AddDate(0, -*monthsBack, 0)\n    if cutoff.After(from) { /* refuse or require --force-partial */ }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Default to full backfills; treat --months-back as an explicit data-loss decision","Use --dry-run to preview the effective range before committing","Document that the watermark stamp makes skipped buckets unrecoverable via cron"],"tags":["go","cli","data-loss","backfill","operator-guard"],"backgroundTag":null,"analyzedSha":"2c0912b6ec764b373d44eeea1e80f0d9f11ab417","analyzedAt":"2026-08-15T13:25:18.241Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}