Merge 5a22326ba4 into 4583f88626
This commit is contained in:
commit
4daf76b988
28
PR_UNHANDLED_REJECTIONS.md
Normal file
28
PR_UNHANDLED_REJECTIONS.md
Normal 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.
|
||||
|
||||
Diego’s 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.
|
||||
17
pr-body.md
Normal file
17
pr-body.md
Normal file
@ -0,0 +1,17 @@
|
||||
## Summary
|
||||
Add a JSON config knob to control unhandled promise rejection behavior in the Gateway/CLI.
|
||||
|
||||
## Motivation
|
||||
Transient network errors (e.g., undici `fetch failed`) can currently terminate the gateway when they surface as unhandled promise rejections. Some operators prefer warn-only behavior to avoid missed replies and restarts.
|
||||
|
||||
## Changes
|
||||
- Add `gateway.unhandledRejections: "warn"|"exit"` to config types + zod schema.
|
||||
- Wire config into `installUnhandledRejectionHandler({ mode })` in `src/index.ts` and `src/cli/run-main.ts`.
|
||||
- Extend handler to accept `mode` and skip `process.exit(1)` when `mode="warn"`.
|
||||
|
||||
## Default behavior
|
||||
Unchanged (defaults to `"exit"`).
|
||||
|
||||
## Acceptance criteria
|
||||
- With `gateway.unhandledRejections="warn"`, unhandled rejections are logged but do not terminate the gateway.
|
||||
- With default config, behavior remains unchanged.
|
||||
@ -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));
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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"),
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -123,7 +123,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;
|
||||
|
||||
@ -155,6 +159,7 @@ export function installUnhandledRejectionHandler(): void {
|
||||
}
|
||||
|
||||
console.error("[moltbot] Unhandled promise rejection:", formatUncaughtError(reason));
|
||||
if (mode === "warn") return;
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user