fix(heartbeat): fallback on invalid notify_cooldown values

This commit is contained in:
William Valentin
2026-02-16 15:24:12 -08:00
parent ab80f305ef
commit 7ce58f5966
3 changed files with 38 additions and 4 deletions
+24
View File
@@ -307,6 +307,30 @@ describe('HeartbeatMonitor', () => {
}));
});
it('falls back to default cooldown when notify_cooldown is invalid', async () => {
const mockSend = vi.fn().mockResolvedValue(undefined);
const mockGet = vi.fn().mockReturnValue({ send: mockSend });
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
const deps = makeDeps({
config: makeConfig({
checks: ['model'],
failure_threshold: 1,
notify_cooldown: 'not-a-duration',
notify: { channel: 'telegram', peer: '123' },
}),
modelRouter: undefined,
channelLookup: { get: mockGet },
});
monitor = new HeartbeatMonitor(deps);
await monitor.runChecks();
expect(mockSend).toHaveBeenCalledTimes(1);
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('invalid notify_cooldown'));
warnSpy.mockRestore();
});
it('recovery notification sent when checks pass after failures', async () => {
const mockSend = vi.fn().mockResolvedValue(undefined);
const mockGet = vi.fn().mockReturnValue({ send: mockSend });
+12 -2
View File
@@ -485,8 +485,18 @@ export class HeartbeatMonitor {
return Date.now() - lastAt >= cooldownMs;
}
private getNotifyCooldownMs(): number {
const raw = this.deps.config.notify_cooldown ?? '30m';
try {
return parseInterval(raw);
} catch {
console.warn(`HeartbeatMonitor: invalid notify_cooldown '${raw}', falling back to 30m`);
return parseInterval('30m');
}
}
private async notifyFailureWithCooldown(text: string, signature: string): Promise<boolean> {
const cooldownMs = parseInterval(this.deps.config.notify_cooldown ?? '30m');
const cooldownMs = this.getNotifyCooldownMs();
const signatureChanged = signature !== this.lastFailureSignature;
const cooldownPassed = this.shouldNotifyByCooldown(this.lastFailureNotificationAt, cooldownMs);
if (!signatureChanged && !cooldownPassed) {
@@ -500,7 +510,7 @@ export class HeartbeatMonitor {
}
private async notifyRecoveryWithCooldown(text: string): Promise<boolean> {
const cooldownMs = parseInterval(this.deps.config.notify_cooldown ?? '30m');
const cooldownMs = this.getNotifyCooldownMs();
const cooldownPassed = this.shouldNotifyByCooldown(this.lastRecoveryNotificationAt, cooldownMs);
if (!cooldownPassed) {
return false;