fix(gateway): install unhandled rejection handler in macOS daemon (#3815)

The gateway daemon entrypoint did not call installUnhandledRejectionHandler(),
causing transient fetch/network errors to crash the process. The CLI and relay
paths already install it; this adds the same protection to the daemon path.

Closes #3815
This commit is contained in:
Richard A 2026-01-29 20:57:32 +04:00
parent cb4b3f74b5
commit bef24dc4b0
3 changed files with 64 additions and 0 deletions

View File

@ -14,6 +14,7 @@ Status: beta.
- Memory Search: allow extra paths for memory indexing. (#3600) Thanks @kira-ariaki.
### Changes
- Gateway: prevent crash on transient fetch/network failures (#3815)
- Providers: add Venice AI integration; update Moonshot Kimi references to kimi-k2.5; update MiniMax API endpoint/format. (#2762, #3064)
- Telegram: quote replies, edit-message action, silent sends, sticker support + vision caching, linkPreview toggle, plugin sendPayload support. (#2900, #2394, #2382, #2548, #1700, #1917)
- Discord: configurable privileged gateway intents for presences/members. (#2266) Thanks @kentaro.

View File

@ -0,0 +1,59 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import process from "node:process";
describe("gateway-daemon unhandled rejection handler", () => {
let exitCalls: Array<string | number | null> = [];
let consoleErrorSpy: ReturnType<typeof vi.spyOn>;
let consoleWarnSpy: ReturnType<typeof vi.spyOn>;
beforeEach(async () => {
exitCalls = [];
vi.spyOn(process, "exit").mockImplementation((code: string | number | null | undefined) => {
if (code !== undefined && code !== null) {
exitCalls.push(code);
}
});
consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
// Install the handler (same import the daemon uses)
const { installUnhandledRejectionHandler } = await import(
"../infra/unhandled-rejections.js"
);
installUnhandledRejectionHandler();
});
afterEach(() => {
vi.restoreAllMocks();
});
it("does NOT exit on transient fetch failures (ECONNRESET)", () => {
const fetchErr = Object.assign(new TypeError("fetch failed"), {
cause: { code: "ECONNRESET" },
});
process.emit("unhandledRejection", fetchErr, Promise.resolve());
expect(exitCalls).toEqual([]);
expect(consoleWarnSpy).toHaveBeenCalledWith(
"[moltbot] Non-fatal unhandled rejection (continuing):",
expect.stringContaining("fetch failed"),
);
});
it("exits on fatal errors like OOM", () => {
const oomErr = Object.assign(new Error("Out of memory"), {
code: "ERR_OUT_OF_MEMORY",
});
process.emit("unhandledRejection", oomErr, Promise.resolve());
expect(exitCalls).toEqual([1]);
expect(consoleErrorSpy).toHaveBeenCalledWith(
"[moltbot] FATAL unhandled rejection:",
expect.stringContaining("Out of memory"),
);
});
});

View File

@ -41,6 +41,10 @@ async function main() {
(globalThis as unknown as { Long?: unknown }).Long = Long;
}
// Prevent crashes on transient fetch/network failures (#3815)
const { installUnhandledRejectionHandler } = await import("../infra/unhandled-rejections.js");
installUnhandledRejectionHandler();
const [
{ loadConfig },
{ startGatewayServer },