From 7bd8c6ad419771953f12b8c82fa4b9b1a119cfe1 Mon Sep 17 00:00:00 2001 From: Koshkoshinsk Date: Sun, 12 Apr 2026 09:32:15 +0000 Subject: [PATCH] fix(v2): retry channel adapter setup on transient network errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A NetworkError during adapter.setup() (e.g. Telegram deleteWebhook hitting a DNS hiccup at boot) would log the failure and immediately give up, leaving the channel permanently dead until the host process was manually restarted — even though the host kept running and other channels worked. Wrap the setup call in a small retry loop with backoff (2s, 5s, 10s) that fires only on NetworkError. Misconfigs (bad tokens, invalid options) still fail fast since they don't surface as NetworkError. Universal across channels — applies to any adapter that throws NetworkError from setup(), not just Telegram. --- src/channels/channel-registry.ts | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/channels/channel-registry.ts b/src/channels/channel-registry.ts index d327d33..a2981a6 100644 --- a/src/channels/channel-registry.ts +++ b/src/channels/channel-registry.ts @@ -4,9 +4,14 @@ * Channels self-register on import. The host calls initChannelAdapters() at startup * to instantiate and set up all registered adapters. */ +import { NetworkError } from '@chat-adapter/shared'; import type { ChannelAdapter, ChannelRegistration, ChannelSetup } from './adapter.js'; import { log } from '../log.js'; +const SETUP_RETRY_DELAYS_MS = [2000, 5000, 10000]; + +const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); + const registry = new Map(); const activeAdapters = new Map(); @@ -49,7 +54,31 @@ export async function initChannelAdapters(setupFn: (adapter: ChannelAdapter) => } const setup = setupFn(adapter); - await adapter.setup(setup); + // Transient network failures during adapter init (e.g. Telegram deleteWebhook + // hitting a DNS hiccup at boot) would otherwise leave the channel permanently + // dead until manual restart. Retry only on NetworkError so misconfigs (bad + // tokens, etc.) still fail fast. + let attempt = 0; + while (true) { + try { + await adapter.setup(setup); + break; + } catch (err) { + if (err instanceof NetworkError && attempt < SETUP_RETRY_DELAYS_MS.length) { + const delay = SETUP_RETRY_DELAYS_MS[attempt]!; + log.warn('Channel adapter setup failed with network error, retrying', { + channel: name, + attempt: attempt + 1, + delayMs: delay, + err: err.message, + }); + await sleep(delay); + attempt += 1; + continue; + } + throw err; + } + } activeAdapters.set(adapter.channelType, adapter); log.info('Channel adapter started', { channel: name, type: adapter.channelType }); } catch (err) {