From 95b67ea9ba10f810ef77623d00a8dec52cd200cb Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 3 May 2026 18:27:26 +0100 Subject: [PATCH] fix: exit message cli on plugin preload failure --- CHANGELOG.md | 1 + src/cli/program/message/helpers.test.ts | 14 ++++++++++++++ src/cli/program/message/helpers.ts | 4 ++-- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 561c4af8855..ec03693445c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ Docs: https://docs.openclaw.ai - Agents/tools: stop treating `tools.deny: ["write"]` as an implicit `apply_patch` deny; operators who want to block patch writes should deny `apply_patch` or `group:fs` explicitly. Fixes #76749. (#76795) Thanks @Nek-12 and @hclsys. - Plugins/release: verify published plugin npm tarballs expose compiled runtime entries after publish, catching TS-only package artifacts before release closeout. Thanks @vincentkoc. +- CLI/message: exit cleanly with a nonzero status when message-command plugin registry loading fails before dispatch, preventing `openclaw-message` children from staying alive after plugin load errors. Fixes #76168. - Gateway/update: recover an installed-but-unloaded macOS LaunchAgent after package updates, rerun Gateway health/version/channel readiness checks, and print restart, reinstall, and rollback guidance before reporting update failure. (#76790) Thanks @jonathanlindsay. - CLI/plugins: explain when a missing plugin command alias belongs to a bundled plugin that is disabled by default, including the `openclaw plugins enable ` repair command. (#76835) - Google Meet: route stateful CLI session commands through the gateway-owned runtime so joined realtime sessions survive after the starting CLI process exits. Fixes #76344. Thanks @coltonharris-wq. diff --git a/src/cli/program/message/helpers.test.ts b/src/cli/program/message/helpers.test.ts index 6939731064d..f5798617177 100644 --- a/src/cli/program/message/helpers.test.ts +++ b/src/cli/program/message/helpers.test.ts @@ -136,6 +136,20 @@ describe("runMessageAction", () => { }); }); + it("exits with failure when plugin registry loading fails before dispatch", async () => { + vi.mocked(ensurePluginRegistryLoaded).mockImplementationOnce(() => { + throw new Error("plugin load failed"); + }); + + await runSendAction(); + + expect(messageCommandMock).not.toHaveBeenCalled(); + expect(errorMock).toHaveBeenCalledWith("Error: plugin load failed"); + expect(exitMock).toHaveBeenCalledOnce(); + expect(exitMock).toHaveBeenCalledWith(1); + expect(exitMock).not.toHaveBeenCalledWith(0); + }); + it("runs gateway_stop hooks before exit when registered", async () => { hasHooksMock.mockReturnValueOnce(true); await runSendAction(); diff --git a/src/cli/program/message/helpers.ts b/src/cli/program/message/helpers.ts index 2ef5a4c0c79..658bc8e5011 100644 --- a/src/cli/program/message/helpers.ts +++ b/src/cli/program/message/helpers.ts @@ -82,12 +82,12 @@ export function createMessageCliHelpers( const runMessageAction = async (action: string, opts: Record) => { setVerbose(Boolean(opts.verbose)); - ensurePluginRegistryLoaded(resolveMessagePluginLoadOptions(opts)); - const deps = createDefaultDeps(); let failed = false; await runCommandWithRuntime( defaultRuntime, async () => { + ensurePluginRegistryLoaded(resolveMessagePluginLoadOptions(opts)); + const deps = createDefaultDeps(); await messageCommand( { ...normalizeMessageOptions(opts),