fix: ensure CLI exits after command completion (#12906)

* fix: ensure CLI exits after command completion

The CLI process would hang indefinitely after commands like
`openclaw gateway restart` completed successfully.  Two root causes:

1. `runCli()` returned without calling `process.exit()` after
   `program.parseAsync()` resolved, and Commander.js does not
   force-exit the process.

2. `daemon-cli/register.ts` eagerly called `createDefaultDeps()`
   which imported all messaging-provider modules, creating persistent
   event-loop handles that prevented natural Node exit.

Changes:
- Add `flushAndExit()` helper that drains stdout/stderr before calling
  `process.exit()`, preventing truncated piped output in CI/scripts.
- Call `flushAndExit()` after both `tryRouteCli()` and
  `program.parseAsync()` resolve.
- Remove unnecessary `void createDefaultDeps()` from daemon-cli
  registration — daemon lifecycle commands never use messaging deps.
- Make `serveAcpGateway()` return a promise that resolves on
  intentional shutdown (SIGINT/SIGTERM), so `openclaw acp` blocks
  `parseAsync` for the bridge lifetime and exits cleanly on signal.
- Handle the returned promise in the standalone main-module entry
  point to avoid unhandled rejections.

Fixes #12904

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: refactor CLI lifecycle and lazy outbound deps (#12906) (thanks @DrCrinkle)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
Taylor Asplund
2026-02-13 15:34:33 -08:00
committed by GitHub
parent 2378d770d1
commit 874ff7089c
7 changed files with 208 additions and 21 deletions

View File

@@ -1,10 +1,10 @@
import type { sendMessageWhatsApp } from "../channels/web/index.js";
import type { sendMessageDiscord } from "../discord/send.js";
import type { sendMessageIMessage } from "../imessage/send.js";
import type { OutboundSendDeps } from "../infra/outbound/deliver.js";
import { logWebSelfId, sendMessageWhatsApp } from "../channels/web/index.js";
import { sendMessageDiscord } from "../discord/send.js";
import { sendMessageIMessage } from "../imessage/send.js";
import { sendMessageSignal } from "../signal/send.js";
import { sendMessageSlack } from "../slack/send.js";
import { sendMessageTelegram } from "../telegram/send.js";
import type { sendMessageSignal } from "../signal/send.js";
import type { sendMessageSlack } from "../slack/send.js";
import type { sendMessageTelegram } from "../telegram/send.js";
export type CliDeps = {
sendMessageWhatsApp: typeof sendMessageWhatsApp;
@@ -17,12 +17,30 @@ export type CliDeps = {
export function createDefaultDeps(): CliDeps {
return {
sendMessageWhatsApp,
sendMessageTelegram,
sendMessageDiscord,
sendMessageSlack,
sendMessageSignal,
sendMessageIMessage,
sendMessageWhatsApp: async (...args) => {
const { sendMessageWhatsApp } = await import("../channels/web/index.js");
return await sendMessageWhatsApp(...args);
},
sendMessageTelegram: async (...args) => {
const { sendMessageTelegram } = await import("../telegram/send.js");
return await sendMessageTelegram(...args);
},
sendMessageDiscord: async (...args) => {
const { sendMessageDiscord } = await import("../discord/send.js");
return await sendMessageDiscord(...args);
},
sendMessageSlack: async (...args) => {
const { sendMessageSlack } = await import("../slack/send.js");
return await sendMessageSlack(...args);
},
sendMessageSignal: async (...args) => {
const { sendMessageSignal } = await import("../signal/send.js");
return await sendMessageSignal(...args);
},
sendMessageIMessage: async (...args) => {
const { sendMessageIMessage } = await import("../imessage/send.js");
return await sendMessageIMessage(...args);
},
};
}
@@ -38,4 +56,4 @@ export function createOutboundSendDeps(deps: CliDeps): OutboundSendDeps {
};
}
export { logWebSelfId };
export { logWebSelfId } from "../web/auth-store.js";