From ee36e12f81eba8d3e5e630feff844e70f69cd399 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 19 Jan 2026 00:15:15 +0000 Subject: [PATCH] fix: log plugin load errors in gateway --- CHANGELOG.md | 3 ++ src/gateway/server-plugins.test.ts | 58 ++++++++++++++++++++++++++++++ src/gateway/server-plugins.ts | 13 +++++-- src/plugins/loader.ts | 10 ++++++ 4 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 src/gateway/server-plugins.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 49e7643e1..500d5090f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ Docs: https://docs.clawd.bot ### Changes - Usage: add `/usage cost` summaries and macOS menu cost submenu with daily charting. +### Fixes +- Plugins: surface plugin load/register/config errors in gateway logs with plugin/source context. + ## 2026.1.18-5 ### Changes diff --git a/src/gateway/server-plugins.test.ts b/src/gateway/server-plugins.test.ts new file mode 100644 index 000000000..b4cf95030 --- /dev/null +++ b/src/gateway/server-plugins.test.ts @@ -0,0 +1,58 @@ +import { describe, expect, test, vi } from "vitest"; +import type { PluginRegistry } from "../plugins/registry.js"; +import type { PluginDiagnostic } from "../plugins/types.js"; +import { loadGatewayPlugins } from "./server-plugins.js"; + +const loadClawdbotPlugins = vi.hoisted(() => vi.fn()); + +vi.mock("../plugins/loader.js", () => ({ + loadClawdbotPlugins, +})); + +const createRegistry = (diagnostics: PluginDiagnostic[]): PluginRegistry => ({ + plugins: [], + tools: [], + hooks: [], + typedHooks: [], + channels: [], + providers: [], + gatewayHandlers: {}, + httpHandlers: [], + cliRegistrars: [], + services: [], + diagnostics, +}); + +describe("loadGatewayPlugins", () => { + test("logs plugin errors with details", () => { + const diagnostics: PluginDiagnostic[] = [ + { + level: "error", + pluginId: "telegram", + source: "/tmp/telegram/index.ts", + message: "failed to load plugin: boom", + }, + ]; + loadClawdbotPlugins.mockReturnValue(createRegistry(diagnostics)); + + const log = { + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + debug: vi.fn(), + }; + + loadGatewayPlugins({ + cfg: {}, + workspaceDir: "/tmp", + log, + coreGatewayHandlers: {}, + baseMethods: [], + }); + + expect(log.error).toHaveBeenCalledWith( + "[plugins] failed to load plugin: boom (plugin=telegram, source=/tmp/telegram/index.ts)", + ); + expect(log.warn).not.toHaveBeenCalled(); + }); +}); diff --git a/src/gateway/server-plugins.ts b/src/gateway/server-plugins.ts index 193b517c8..ce5728342 100644 --- a/src/gateway/server-plugins.ts +++ b/src/gateway/server-plugins.ts @@ -29,10 +29,19 @@ export function loadGatewayPlugins(params: { const gatewayMethods = Array.from(new Set([...params.baseMethods, ...pluginMethods])); if (pluginRegistry.diagnostics.length > 0) { for (const diag of pluginRegistry.diagnostics) { + const details = [ + diag.pluginId ? `plugin=${diag.pluginId}` : null, + diag.source ? `source=${diag.source}` : null, + ] + .filter((entry): entry is string => Boolean(entry)) + .join(", "); + const message = details + ? `[plugins] ${diag.message} (${details})` + : `[plugins] ${diag.message}`; if (diag.level === "error") { - params.log.warn(`[plugins] ${diag.message}`); + params.log.error(message); } else { - params.log.info(`[plugins] ${diag.message}`); + params.log.info(message); } } } diff --git a/src/plugins/loader.ts b/src/plugins/loader.ts index 4ea8c1816..2d316439d 100644 --- a/src/plugins/loader.ts +++ b/src/plugins/loader.ts @@ -376,6 +376,9 @@ export function loadClawdbotPlugins(options: PluginLoadOptions = {}): PluginRegi try { mod = jiti(candidate.source) as ClawdbotPluginModule; } catch (err) { + logger.error( + `[plugins] ${record.id} failed to load from ${record.source}: ${String(err)}`, + ); record.status = "error"; record.error = String(err); registry.plugins.push(record); @@ -460,6 +463,9 @@ export function loadClawdbotPlugins(options: PluginLoadOptions = {}): PluginRegi }); if (!validatedConfig.ok) { + logger.error( + `[plugins] ${record.id} invalid config: ${validatedConfig.errors?.join(", ")}`, + ); record.status = "error"; record.error = `invalid config: ${validatedConfig.errors?.join(", ")}`; registry.plugins.push(record); @@ -474,6 +480,7 @@ export function loadClawdbotPlugins(options: PluginLoadOptions = {}): PluginRegi } if (typeof register !== "function") { + logger.error(`[plugins] ${record.id} missing register/activate export`); record.status = "error"; record.error = "plugin export missing register/activate"; registry.plugins.push(record); @@ -505,6 +512,9 @@ export function loadClawdbotPlugins(options: PluginLoadOptions = {}): PluginRegi registry.plugins.push(record); seenIds.set(candidate.idHint, candidate.origin); } catch (err) { + logger.error( + `[plugins] ${record.id} failed during register from ${record.source}: ${String(err)}`, + ); record.status = "error"; record.error = String(err); registry.plugins.push(record);