From 0e4373d7acfa4f31f9332f4dcf1c9f2b470a96ee Mon Sep 17 00:00:00 2001 From: RealDiligent Date: Thu, 9 Jul 2026 01:35:17 +0800 Subject: [PATCH] fix: resolve runaway PR reconcile loop from invalid env values Co-authored-by: Cursor --- .../das/src/webhook/pr-reconcile.service.ts | 51 ++++++++++++++++--- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/packages/das/src/webhook/pr-reconcile.service.ts b/packages/das/src/webhook/pr-reconcile.service.ts index ea755af..a4bbbd9 100644 --- a/packages/das/src/webhook/pr-reconcile.service.ts +++ b/packages/das/src/webhook/pr-reconcile.service.ts @@ -25,11 +25,39 @@ const RECONCILE_INTERVAL_MS = Number( const RECONCILE_WINDOW_DAYS = Number( process.env.PR_RECONCILE_WINDOW_DAYS ?? 45, ); +const MIN_RECONCILE_INTERVAL_MS = 60_000; +const MAX_RECONCILE_INTERVAL_MS = 24 * 60 * 60 * 1000; +const MIN_RECONCILE_WINDOW_DAYS = 1; +const MAX_RECONCILE_WINDOW_DAYS = 365; + +function parseBoundedNumber( + rawValue: number, + defaultValue: number, + minValue: number, + maxValue: number, +): number { + if (!Number.isFinite(rawValue)) return defaultValue; + const normalized = Math.floor(rawValue); + if (normalized < minValue || normalized > maxValue) return defaultValue; + return normalized; +} @Injectable() export class PrReconcileService implements OnModuleInit, OnModuleDestroy { private readonly logger = new Logger(PrReconcileService.name); private timer: NodeJS.Timeout | null = null; + private readonly reconcileIntervalMs = parseBoundedNumber( + RECONCILE_INTERVAL_MS, + 60 * 60 * 1000, + MIN_RECONCILE_INTERVAL_MS, + MAX_RECONCILE_INTERVAL_MS, + ); + private readonly reconcileWindowDays = parseBoundedNumber( + RECONCILE_WINDOW_DAYS, + 45, + MIN_RECONCILE_WINDOW_DAYS, + MAX_RECONCILE_WINDOW_DAYS, + ); constructor( @InjectRepository(PullRequest) @@ -39,12 +67,22 @@ export class PrReconcileService implements OnModuleInit, OnModuleDestroy { ) {} onModuleInit(): void { + if (this.reconcileIntervalMs !== RECONCILE_INTERVAL_MS) { + this.logger.warn( + `Invalid PR_RECONCILE_INTERVAL_MS=${String(process.env.PR_RECONCILE_INTERVAL_MS)}; ` + + `using ${this.reconcileIntervalMs}ms`, + ); + } + if (this.reconcileWindowDays !== RECONCILE_WINDOW_DAYS) { + this.logger.warn( + `Invalid PR_RECONCILE_WINDOW_DAYS=${String(process.env.PR_RECONCILE_WINDOW_DAYS)}; ` + + `using ${this.reconcileWindowDays} days`, + ); + } + // Run once at startup, then on the interval. void this.reconcile(); - this.timer = setInterval( - () => void this.reconcile(), - RECONCILE_INTERVAL_MS, - ); + this.timer = setInterval(() => void this.reconcile(), this.reconcileIntervalMs); } onModuleDestroy(): void { @@ -60,12 +98,13 @@ export class PrReconcileService implements OnModuleInit, OnModuleDestroy { JOIN repos r ON r.repo_full_name = p.repo_full_name WHERE p.state = 'OPEN' AND r.registered = true - AND p.created_at > NOW() - INTERVAL '${RECONCILE_WINDOW_DAYS} days'`, + AND p.created_at > NOW() - make_interval(days => $1::int)`, + [this.reconcileWindowDays], ); this.logger.log( `Reconciling ${rows.length} open PRs against GitHub ` + - `(window ${RECONCILE_WINDOW_DAYS}d)`, + `(window ${this.reconcileWindowDays}d)`, ); for (const row of rows) {