fix: bound message CLI shutdown hooks

This commit is contained in:
Peter Steinberger
2026-05-02 08:30:25 +01:00
parent f2782c941e
commit 2c14d6f99d
3 changed files with 59 additions and 2 deletions

View File

@@ -144,6 +144,41 @@ describe("runMessageAction", () => {
expect(exitMock).toHaveBeenCalledWith(0);
});
it("skips gateway_stop hooks for read-only message reads", async () => {
hasHooksMock.mockReturnValueOnce(true);
const runMessageAction = createRunMessageAction();
await expect(
runMessageAction("read", {
channel: "discord",
target: "channel:123",
limit: 1,
}),
).rejects.toThrow("exit");
expect(runGlobalGatewayStopSafelyMock).not.toHaveBeenCalled();
expect(runGatewayStopMock).not.toHaveBeenCalled();
expect(exitMock).toHaveBeenCalledWith(0);
});
it("bounds gateway_stop hooks so message actions still exit", async () => {
vi.useFakeTimers();
try {
hasHooksMock.mockReturnValueOnce(true);
runGatewayStopMock.mockImplementationOnce(() => new Promise(() => undefined));
const runMessageAction = createRunMessageAction();
const pending = expect(runMessageAction("send", baseSendOptions)).rejects.toThrow("exit");
await vi.advanceTimersByTimeAsync(2500);
await pending;
expect(errorMock).toHaveBeenCalledWith("gateway_stop hook exceeded 2500ms; continuing");
expect(exitMock).toHaveBeenCalledWith(0);
} finally {
vi.useRealTimers();
}
});
it("calls exit(1) when message delivery fails", async () => {
messageCommandMock.mockRejectedValueOnce(new Error("send failed"));
await runSendAction();

View File

@@ -16,6 +16,9 @@ export type MessageCliHelpers = {
runMessageAction: (action: string, opts: Record<string, unknown>) => Promise<void>;
};
const GATEWAY_STOP_TIMEOUT_MS = 2500;
const ACTIONS_WITHOUT_STOP_HOOKS = new Set(["read"]);
function normalizeMessageOptions(opts: Record<string, unknown>): Record<string, unknown> {
const { account, ...rest } = opts;
return {
@@ -25,11 +28,25 @@ function normalizeMessageOptions(opts: Record<string, unknown>): Record<string,
}
async function runPluginStopHooks(): Promise<void> {
await runGlobalGatewayStopSafely({
let timeout: NodeJS.Timeout | null = null;
const hookRun = runGlobalGatewayStopSafely({
event: { reason: "cli message action complete" },
ctx: {},
onError: (err) => defaultRuntime.error(danger(`gateway_stop hook failed: ${String(err)}`)),
});
const bounded = new Promise<"timeout">((resolve) => {
timeout = setTimeout(() => resolve("timeout"), GATEWAY_STOP_TIMEOUT_MS);
timeout.unref?.();
});
const result = await Promise.race([hookRun.then(() => "done" as const), bounded]);
if (timeout) {
clearTimeout(timeout);
}
if (result === "timeout") {
defaultRuntime.error(
danger(`gateway_stop hook exceeded ${GATEWAY_STOP_TIMEOUT_MS}ms; continuing`),
);
}
}
function resolveMessagePluginLoadOptions(
@@ -85,7 +102,9 @@ export function createMessageCliHelpers(
defaultRuntime.error(danger(String(err)));
},
);
await runPluginStopHooks();
if (!ACTIONS_WITHOUT_STOP_HOOKS.has(action)) {
await runPluginStopHooks();
}
defaultRuntime.exit(failed ? 1 : 0);
};