From e81ca7ab00ae8bf325691154cb3d8e2f410936ea Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 21 Jan 2026 01:58:08 +0000 Subject: [PATCH] fix: tame invalid config logging --- CHANGELOG.md | 1 + src/cli/run-main.ts | 3 ++- src/config/io.ts | 11 +++++++++-- src/index.ts | 8 +++----- src/infra/errors.ts | 10 ++++++++++ src/infra/unhandled-rejections.ts | 7 +++---- src/macos/relay.ts | 3 ++- 7 files changed, 30 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47180b133..00e5f147e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ Docs: https://docs.clawd.bot - Gateway: clarify unauthorized handshake responses with token/password mismatch guidance. - Gateway: preserve restart wake routing + thread replies across restarts. (#1337) — thanks @John-Rood. - Gateway: reschedule per-agent heartbeats on config hot reload without restarting the runner. +- Config: log invalid config issues once per run and keep invalid-config errors stackless. - UI: keep config form enums typed, preserve empty strings, protect sensitive defaults, and deepen config search. (#1315) — thanks @MaudeBot. - UI: preserve ordered list numbering in chat markdown. (#1341) — thanks @bradleypriest. - UI: allow Control UI to read gatewayUrl from URL params for remote WebSocket targets. (#1342) — thanks @ameno-. diff --git a/src/cli/run-main.ts b/src/cli/run-main.ts index ff12ecfbb..84c6f3553 100644 --- a/src/cli/run-main.ts +++ b/src/cli/run-main.ts @@ -7,6 +7,7 @@ import { normalizeEnv } from "../infra/env.js"; import { isMainModule } from "../infra/is-main.js"; import { ensureClawdbotCliOnPath } from "../infra/path-env.js"; import { assertSupportedRuntime } from "../infra/runtime-guard.js"; +import { formatUncaughtError } from "../infra/errors.js"; import { installUnhandledRejectionHandler } from "../infra/unhandled-rejections.js"; import { enableConsoleCapture } from "../logging.js"; import { tryRouteCli } from "./route.js"; @@ -42,7 +43,7 @@ export async function runCli(argv: string[] = process.argv) { installUnhandledRejectionHandler(); process.on("uncaughtException", (error) => { - console.error("[clawdbot] Uncaught exception:", error.stack ?? error.message); + console.error("[clawdbot] Uncaught exception:", formatUncaughtError(error)); process.exit(1); }); diff --git a/src/config/io.ts b/src/config/io.ts index b2302a7c4..d275d3185 100644 --- a/src/config/io.ts +++ b/src/config/io.ts @@ -57,6 +57,7 @@ const SHELL_ENV_EXPECTED_KEYS = [ ]; const CONFIG_BACKUP_COUNT = 5; +const loggedInvalidConfigs = new Set(); export type ParseConfigJson5Result = { ok: true; parsed: unknown } | { ok: false; error: string }; @@ -244,8 +245,14 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) { const details = validated.issues .map((iss) => `- ${iss.path || ""}: ${iss.message}`) .join("\n"); - deps.logger.error(`Invalid config:\\n${details}`); - throw new Error("Invalid config"); + if (!loggedInvalidConfigs.has(configPath)) { + loggedInvalidConfigs.add(configPath); + deps.logger.error(`Invalid config:\\n${details}`); + } + const error = new Error("Invalid config"); + (error as { code?: string; details?: string }).code = "INVALID_CONFIG"; + (error as { code?: string; details?: string }).details = details; + throw error; } if (validated.warnings.length > 0) { const details = validated.warnings diff --git a/src/index.ts b/src/index.ts index c4b2ff14a..4476de37f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -28,6 +28,7 @@ import { PortInUseError, } from "./infra/ports.js"; import { assertSupportedRuntime } from "./infra/runtime-guard.js"; +import { formatUncaughtError } from "./infra/errors.js"; import { installUnhandledRejectionHandler } from "./infra/unhandled-rejections.js"; import { enableConsoleCapture } from "./logging.js"; import { runCommandWithTimeout, runExec } from "./process/exec.js"; @@ -82,15 +83,12 @@ if (isMain) { installUnhandledRejectionHandler(); process.on("uncaughtException", (error) => { - console.error("[clawdbot] Uncaught exception:", error.stack ?? error.message); + console.error("[clawdbot] Uncaught exception:", formatUncaughtError(error)); process.exit(1); }); void program.parseAsync(process.argv).catch((err) => { - console.error( - "[clawdbot] CLI failed:", - err instanceof Error ? (err.stack ?? err.message) : err, - ); + console.error("[clawdbot] CLI failed:", formatUncaughtError(err)); process.exit(1); }); } diff --git a/src/infra/errors.ts b/src/infra/errors.ts index eaf7619de..9175b3622 100644 --- a/src/infra/errors.ts +++ b/src/infra/errors.ts @@ -20,3 +20,13 @@ export function formatErrorMessage(err: unknown): string { return Object.prototype.toString.call(err); } } + +export function formatUncaughtError(err: unknown): string { + if (extractErrorCode(err) === "INVALID_CONFIG") { + return formatErrorMessage(err); + } + if (err instanceof Error) { + return err.stack ?? err.message ?? err.name; + } + return formatErrorMessage(err); +} diff --git a/src/infra/unhandled-rejections.ts b/src/infra/unhandled-rejections.ts index 80b4b627e..c444baaa2 100644 --- a/src/infra/unhandled-rejections.ts +++ b/src/infra/unhandled-rejections.ts @@ -1,5 +1,7 @@ import process from "node:process"; +import { formatUncaughtError } from "./errors.js"; + type UnhandledRejectionHandler = (reason: unknown) => boolean; const handlers = new Set(); @@ -28,10 +30,7 @@ export function isUnhandledRejectionHandled(reason: unknown): boolean { export function installUnhandledRejectionHandler(): void { process.on("unhandledRejection", (reason, _promise) => { if (isUnhandledRejectionHandled(reason)) return; - console.error( - "[clawdbot] Unhandled promise rejection:", - reason instanceof Error ? (reason.stack ?? reason.message) : reason, - ); + console.error("[clawdbot] Unhandled promise rejection:", formatUncaughtError(reason)); process.exit(1); }); } diff --git a/src/macos/relay.ts b/src/macos/relay.ts index 4e1d34bda..03c34340c 100644 --- a/src/macos/relay.ts +++ b/src/macos/relay.ts @@ -55,6 +55,7 @@ async function main() { const { assertSupportedRuntime } = await import("../infra/runtime-guard.js"); assertSupportedRuntime(); + const { formatUncaughtError } = await import("../infra/errors.js"); const { installUnhandledRejectionHandler } = await import("../infra/unhandled-rejections.js"); const { buildProgram } = await import("../cli/program.js"); @@ -63,7 +64,7 @@ async function main() { installUnhandledRejectionHandler(); process.on("uncaughtException", (error) => { - console.error("[clawdbot] Uncaught exception:", error.stack ?? error.message); + console.error("[clawdbot] Uncaught exception:", formatUncaughtError(error)); process.exit(1); });