From e4f9555f210141e991a48d2d90c6ef31af5897db Mon Sep 17 00:00:00 2001 From: Doug von Kohorn Date: Tue, 20 Jan 2026 20:09:55 +0100 Subject: [PATCH] fix(model-catalog): avoid caching import failures Move dynamic import of @mariozechner/pi-coding-agent into the try/catch so transient module resolution errors don't poison the model catalog cache with a rejected promise. This previously caused Discord/Telegram handlers and heartbeat to fail until process restart if the import failed once. --- src/agents/model-catalog.ts | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/agents/model-catalog.ts b/src/agents/model-catalog.ts index c7bb01fd9..8043eb9b6 100644 --- a/src/agents/model-catalog.ts +++ b/src/agents/model-catalog.ts @@ -34,10 +34,14 @@ export async function loadModelCatalog(params?: { if (modelCatalogPromise) return modelCatalogPromise; modelCatalogPromise = (async () => { - const piSdk = await import("@mariozechner/pi-coding-agent"); - - const models: ModelCatalogEntry[] = []; try { + // IMPORTANT: keep the dynamic import *inside* the try/catch. + // If this fails once (e.g. during a pnpm install that temporarily swaps node_modules), + // we must not poison the cache with a rejected promise (otherwise all channel handlers + // will keep failing until restart). + const piSdk = await import("@mariozechner/pi-coding-agent"); + + const models: ModelCatalogEntry[] = []; const cfg = params?.config ?? loadConfig(); await ensureClawdbotModelsJson(cfg); const agentDir = resolveClawdbotAgentDir(); @@ -66,16 +70,17 @@ export async function loadModelCatalog(params?: { // If we found nothing, don't cache this result so we can try again. modelCatalogPromise = null; } - } catch { - // Leave models empty on discovery errors and don't cache. - modelCatalogPromise = null; - } - return models.sort((a, b) => { - const p = a.provider.localeCompare(b.provider); - if (p !== 0) return p; - return a.name.localeCompare(b.name); - }); + return models.sort((a, b) => { + const p = a.provider.localeCompare(b.provider); + if (p !== 0) return p; + return a.name.localeCompare(b.name); + }); + } catch { + // Don't poison the cache on transient dependency/filesystem issues. + modelCatalogPromise = null; + return []; + } })(); return modelCatalogPromise;