From db1335bda70d3eb44efd2aeef3de9446b9e9b254 Mon Sep 17 00:00:00 2001 From: anderdc Date: Wed, 24 Jun 2026 11:09:48 -0500 Subject: [PATCH 1/3] fix(backfill): anchor nightly backfill to a fixed wall clock via cron MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The nightly backfill used setInterval(24h) registered in onModuleInit, so its fire time was boot + N*24h — anchored to whatever time the process last restarted. Every redeploy silently moved the window (most recently to ~03:35 UTC), making the schedule unpredictable and the verification window a moving target. Switch to @nestjs/schedule's @Cron (ScheduleModule is already wired up and used by MaintainerPopulateService). Default '10 0 * * *' in America/Chicago = 12:10am local, stable across redeploys, with timeZone handling the CST/CDT shift so it stays at local midnight year-round. Overridable via NIGHTLY_BACKFILL_CRON / NIGHTLY_BACKFILL_TZ; the :10 offset preserves the prior 00:10 stagger. Behavior preserved: still no run-at-startup, NIGHTLY_BACKFILL_ENABLED still disables it, and the static per-repo jobId still dedupes overlapping ticks. Replaces the removed NIGHTLY_BACKFILL_INTERVAL_MS knob in .env.example. --- .env.example | 4 +- .../webhook/repo-backfill-schedule.service.ts | 41 +++++++++---------- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/.env.example b/.env.example index 0de88aa..48760c6 100644 --- a/.env.example +++ b/.env.example @@ -30,6 +30,8 @@ PR_RECONCILE_INTERVAL_MS=3600000 PR_RECONCILE_WINDOW_DAYS=45 # Nightly full repo backfill (coarse safety net; heavier — set false to disable). +# Anchored to a fixed wall clock via cron (timeZone-aware), not boot time. NIGHTLY_BACKFILL_ENABLED=true -NIGHTLY_BACKFILL_INTERVAL_MS=86400000 +NIGHTLY_BACKFILL_CRON=10 0 * * * +NIGHTLY_BACKFILL_TZ=America/Chicago NIGHTLY_BACKFILL_DAYS=40 diff --git a/packages/das/src/webhook/repo-backfill-schedule.service.ts b/packages/das/src/webhook/repo-backfill-schedule.service.ts index 2494605..3f26bc6 100644 --- a/packages/das/src/webhook/repo-backfill-schedule.service.ts +++ b/packages/das/src/webhook/repo-backfill-schedule.service.ts @@ -1,9 +1,5 @@ -import { - Injectable, - Logger, - OnModuleDestroy, - OnModuleInit, -} from "@nestjs/common"; +import { Injectable, Logger, OnModuleInit } from "@nestjs/common"; +import { Cron } from "@nestjs/schedule"; import { InjectQueue } from "@nestjs/bullmq"; import { InjectRepository } from "@nestjs/typeorm"; import { Queue } from "bullmq"; @@ -22,19 +18,21 @@ import { // Heavier than the reconcile sweep (re-touches every PR in the window), so it // runs daily and can be disabled on critical infra via env. const BACKFILL_ENABLED = process.env.NIGHTLY_BACKFILL_ENABLED !== "false"; -const BACKFILL_INTERVAL_MS = Number( - process.env.NIGHTLY_BACKFILL_INTERVAL_MS ?? 24 * 60 * 60 * 1000, // daily -); +// Anchor to a fixed wall clock, not boot. A boot-anchored `setInterval(24h)` +// re-pins the nightly to whatever time the process last restarted, so every +// redeploy silently moves the window. A cron expression fires at the same real +// time regardless of boot, and `timeZone` handles the CST/CDT shift so it stays +// at local midnight year-round. Default: 12:10am America/Chicago (the :10 keeps +// it off the top of the hour, preserving the prior 00:10 stagger). +const BACKFILL_CRON = process.env.NIGHTLY_BACKFILL_CRON ?? "10 0 * * *"; +const BACKFILL_TZ = process.env.NIGHTLY_BACKFILL_TZ ?? "America/Chicago"; const BACKFILL_DAYS = Number( process.env.NIGHTLY_BACKFILL_DAYS ?? DEFAULT_BACKFILL_DAYS, ); @Injectable() -export class RepoBackfillScheduleService - implements OnModuleInit, OnModuleDestroy -{ +export class RepoBackfillScheduleService implements OnModuleInit { private readonly logger = new Logger(RepoBackfillScheduleService.name); - private timer: NodeJS.Timeout | null = null; constructor( @InjectRepository(Repo) @@ -50,19 +48,18 @@ export class RepoBackfillScheduleService ); return; } - // Unlike the reconcile sweep, don't run at startup — a deploy already - // implies fresh data, and this is the heavy job. Start on the interval. - this.timer = setInterval( - () => void this.backfillAll(), - BACKFILL_INTERVAL_MS, + this.logger.log( + `Nightly repo backfill scheduled '${BACKFILL_CRON}' (${BACKFILL_TZ})`, ); } - onModuleDestroy(): void { - if (this.timer) clearInterval(this.timer); - } - + // Fires on the fixed wall clock above. Unlike the reconcile sweep, it does + // not run at startup — a deploy already implies fresh data, and this is the + // heavy job. The static per-repo jobId in backfillAll dedupes a tick that + // lands while the prior night's run is still draining. + @Cron(BACKFILL_CRON, { name: "nightly-backfill", timeZone: BACKFILL_TZ }) private async backfillAll(): Promise { + if (!BACKFILL_ENABLED) return; try { const repos = await this.repoRepo.find({ where: { registered: true }, From dd871baeef595fea49aa33391e5677201c478483 Mon Sep 17 00:00:00 2001 From: anderdc Date: Wed, 24 Jun 2026 11:37:08 -0500 Subject: [PATCH 2/3] refactor(backfill): hardcode cron schedule, trim comments The cron expression and timezone won't realistically change, so drop the NIGHTLY_BACKFILL_CRON / NIGHTLY_BACKFILL_TZ env indirection and make them plain constants. Condense the surrounding comments. --- .env.example | 4 +--- .../webhook/repo-backfill-schedule.service.ts | 18 ++++++------------ 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/.env.example b/.env.example index 48760c6..49e58b2 100644 --- a/.env.example +++ b/.env.example @@ -30,8 +30,6 @@ PR_RECONCILE_INTERVAL_MS=3600000 PR_RECONCILE_WINDOW_DAYS=45 # Nightly full repo backfill (coarse safety net; heavier — set false to disable). -# Anchored to a fixed wall clock via cron (timeZone-aware), not boot time. +# Runs at 12:10am America/Chicago (hardcoded cron); window in days below. NIGHTLY_BACKFILL_ENABLED=true -NIGHTLY_BACKFILL_CRON=10 0 * * * -NIGHTLY_BACKFILL_TZ=America/Chicago NIGHTLY_BACKFILL_DAYS=40 diff --git a/packages/das/src/webhook/repo-backfill-schedule.service.ts b/packages/das/src/webhook/repo-backfill-schedule.service.ts index 3f26bc6..f7c4c49 100644 --- a/packages/das/src/webhook/repo-backfill-schedule.service.ts +++ b/packages/das/src/webhook/repo-backfill-schedule.service.ts @@ -18,14 +18,10 @@ import { // Heavier than the reconcile sweep (re-touches every PR in the window), so it // runs daily and can be disabled on critical infra via env. const BACKFILL_ENABLED = process.env.NIGHTLY_BACKFILL_ENABLED !== "false"; -// Anchor to a fixed wall clock, not boot. A boot-anchored `setInterval(24h)` -// re-pins the nightly to whatever time the process last restarted, so every -// redeploy silently moves the window. A cron expression fires at the same real -// time regardless of boot, and `timeZone` handles the CST/CDT shift so it stays -// at local midnight year-round. Default: 12:10am America/Chicago (the :10 keeps -// it off the top of the hour, preserving the prior 00:10 stagger). -const BACKFILL_CRON = process.env.NIGHTLY_BACKFILL_CRON ?? "10 0 * * *"; -const BACKFILL_TZ = process.env.NIGHTLY_BACKFILL_TZ ?? "America/Chicago"; +// 12:10am America/Chicago. Anchored to the wall clock (not boot) so redeploys +// don't move the window; timeZone keeps it at local midnight across CST/CDT. +const BACKFILL_CRON = "10 0 * * *"; +const BACKFILL_TZ = "America/Chicago"; const BACKFILL_DAYS = Number( process.env.NIGHTLY_BACKFILL_DAYS ?? DEFAULT_BACKFILL_DAYS, ); @@ -53,10 +49,8 @@ export class RepoBackfillScheduleService implements OnModuleInit { ); } - // Fires on the fixed wall clock above. Unlike the reconcile sweep, it does - // not run at startup — a deploy already implies fresh data, and this is the - // heavy job. The static per-repo jobId in backfillAll dedupes a tick that - // lands while the prior night's run is still draining. + // No run-at-startup (a deploy already implies fresh data, and this is heavy); + // the static per-repo jobId dedupes a tick that overlaps a draining run. @Cron(BACKFILL_CRON, { name: "nightly-backfill", timeZone: BACKFILL_TZ }) private async backfillAll(): Promise { if (!BACKFILL_ENABLED) return; From fcbe4d073f6e5048118db6df69781a827e382188 Mon Sep 17 00:00:00 2001 From: anderdc Date: Wed, 24 Jun 2026 12:22:53 -0500 Subject: [PATCH 3/3] docs(env): sync NIGHTLY_BACKFILL_DAYS example to code default (40->10) PR #195 dropped DEFAULT_BACKFILL_DAYS 40->10 but .env.example kept the stale 40. --- .env.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 49e58b2..85b26c0 100644 --- a/.env.example +++ b/.env.example @@ -32,4 +32,4 @@ PR_RECONCILE_WINDOW_DAYS=45 # Nightly full repo backfill (coarse safety net; heavier — set false to disable). # Runs at 12:10am America/Chicago (hardcoded cron); window in days below. NIGHTLY_BACKFILL_ENABLED=true -NIGHTLY_BACKFILL_DAYS=40 +NIGHTLY_BACKFILL_DAYS=10