Add gateway.unhandledRejections config (warn|exit)

This commit is contained in:
root 2026-01-27 22:13:25 +00:00
parent 0b1c8db0ca
commit 5dfe2eacc9
6 changed files with 53 additions and 5 deletions

View File

@ -0,0 +1,28 @@
# Proposal: Configurable gateway unhandled promise rejection policy
## Motivation
Running Clawdbot Gateway in production-like environments can encounter transient network errors (undici `fetch failed`, DNS hiccups, Telegram API blips). Today, any *unhandled* promise rejection can terminate the gateway process (`process.exit(1)`), causing user-visible downtime and missed replies.
Diegos deployment uses systemd with `Restart=always`, but the restart still interrupts message handling.
## Desired behavior
Add a JSON config knob to control what happens on unhandled promise rejections:
```json5
{
gateway: {
unhandledRejections: "warn" // or "exit"
}
}
```
- `exit` (default): keep current behavior for safety; exit 1 so supervisors restart.
- `warn`: never exit the gateway for unhandled rejections; log as error/warn and continue.
## Notes
- Existing suppression for AbortError / transient network errors should remain regardless of mode.
- In `warn` mode, non-network/unexpected unhandled rejections should still be logged loudly.
## Acceptance criteria
- With `gateway.unhandledRejections="warn"`, a forced Telegram DNS failure should result in a logged error but the gateway process should remain running.
- With `exit` (default), behavior remains unchanged.

View File

@ -10,6 +10,7 @@ import { ensureMoltbotCliOnPath } 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 { loadConfig } from "../config/config.js";
import { enableConsoleCapture } from "../logging.js";
import { getPrimaryCommand, hasHelpOrVersion } from "./argv.js";
import { tryRouteCli } from "./route.js";
@ -41,8 +42,10 @@ export async function runCli(argv: string[] = process.argv) {
const program = buildProgram();
// Global error handlers to prevent silent crashes from unhandled rejections/exceptions.
// These log the error and exit gracefully instead of crashing without trace.
installUnhandledRejectionHandler();
// Default behavior is "exit" (preserves historical behavior). Users can opt into
// "warn" via config: gateway.unhandledRejections.
const cfg = loadConfig();
installUnhandledRejectionHandler({ mode: cfg.gateway?.unhandledRejections ?? "exit" });
process.on("uncaughtException", (error) => {
console.error("[moltbot] Uncaught exception:", formatUncaughtError(error));

View File

@ -205,9 +205,17 @@ export type GatewayNodesConfig = {
denyCommands?: string[];
};
export type GatewayUnhandledRejectionsMode = "exit" | "warn";
export type GatewayConfig = {
/** Single multiplexed port for Gateway WS + HTTP (default: 18789). */
port?: number;
/**
* What to do on unhandled promise rejections in the gateway process.
* - exit (default): log + exit(1)
* - warn: log but keep running
*/
unhandledRejections?: GatewayUnhandledRejectionsMode;
/**
* Explicit gateway mode. When set to "remote", local gateway start is disabled.
* When set to "local", the CLI may start the gateway locally.

View File

@ -304,6 +304,7 @@ export const MoltbotSchema = z
.object({
port: z.number().int().positive().optional(),
mode: z.union([z.literal("local"), z.literal("remote")]).optional(),
unhandledRejections: z.union([z.literal("exit"), z.literal("warn")]).optional(),
bind: z
.union([
z.literal("auto"),

View File

@ -30,6 +30,7 @@ import {
import { assertSupportedRuntime } from "./infra/runtime-guard.js";
import { formatUncaughtError } from "./infra/errors.js";
import { installUnhandledRejectionHandler } from "./infra/unhandled-rejections.js";
import { loadConfig } from "./config/config.js";
import { enableConsoleCapture } from "./logging.js";
import { runCommandWithTimeout, runExec } from "./process/exec.js";
import { assertWebChannel, normalizeE164, toWhatsappJid } from "./utils.js";
@ -79,8 +80,10 @@ const isMain = isMainModule({
if (isMain) {
// Global error handlers to prevent silent crashes from unhandled rejections/exceptions.
// These log the error and exit gracefully instead of crashing without trace.
installUnhandledRejectionHandler();
// Default behavior is "exit" (preserves historical behavior). Users can opt into
// "warn" via config: gateway.unhandledRejections.
const cfg = loadConfig();
installUnhandledRejectionHandler({ mode: cfg.gateway?.unhandledRejections ?? "exit" });
process.on("uncaughtException", (error) => {
console.error("[moltbot] Uncaught exception:", formatUncaughtError(error));

View File

@ -104,7 +104,11 @@ export function isUnhandledRejectionHandled(reason: unknown): boolean {
return false;
}
export function installUnhandledRejectionHandler(): void {
export type UnhandledRejectionsMode = "exit" | "warn";
export function installUnhandledRejectionHandler(opts: { mode?: UnhandledRejectionsMode } = {}): void {
const mode: UnhandledRejectionsMode = opts.mode ?? "exit";
process.on("unhandledRejection", (reason, _promise) => {
if (isUnhandledRejectionHandled(reason)) return;
@ -123,6 +127,7 @@ export function installUnhandledRejectionHandler(): void {
}
console.error("[moltbot] Unhandled promise rejection:", formatUncaughtError(reason));
if (mode === "warn") return;
process.exit(1);
});
}