fix: handle intentional signal daemon shutdown on abort (#23379) (thanks @frankekn)

This commit is contained in:
Peter Steinberger
2026-02-22 10:59:06 +01:00
parent 1051f42f96
commit 602a1ebd55
3 changed files with 48 additions and 2 deletions

View File

@@ -246,6 +246,43 @@ describe("monitorSignalProvider tool results", () => {
).rejects.toThrow(/signal daemon exited/i);
});
it("treats daemon exit after user abort as clean shutdown", async () => {
const runtime = createMonitorRuntime();
setSignalAutoStartConfig();
const abortController = new AbortController();
let exited = false;
let resolveExit!: (value: { code: number | null; signal: NodeJS.Signals | null }) => void;
const exitedPromise = new Promise<{ code: number | null; signal: NodeJS.Signals | null }>(
(resolve) => {
resolveExit = resolve;
},
);
const stop = vi.fn(() => {
if (exited) {
return;
}
exited = true;
resolveExit({ code: null, signal: "SIGTERM" });
});
spawnSignalDaemonMock.mockReturnValueOnce({
stop,
exited: exitedPromise,
isExited: () => exited,
});
streamMock.mockImplementationOnce(async () => {
abortController.abort(new Error("stop"));
});
await expect(
runMonitorWithMocks({
autoStart: true,
baseUrl: SIGNAL_BASE_URL,
runtime,
abortSignal: abortController.signal,
}),
).resolves.toBeUndefined();
});
it("skips tool summaries with responsePrefix", async () => {
replyMock.mockResolvedValue({ text: "final reply" });

View File

@@ -330,6 +330,11 @@ export async function monitorSignalProvider(opts: MonitorSignalOpts = {}): Promi
const daemonAbortController = new AbortController();
const mergedAbort = mergeAbortSignals(opts.abortSignal, daemonAbortController.signal);
let daemonHandle: ReturnType<typeof spawnSignalDaemon> | null = null;
let daemonStopRequested = false;
const stopDaemon = () => {
daemonStopRequested = true;
daemonHandle?.stop();
};
if (autoStart) {
const cliPath = opts.cliPath ?? accountInfo.config.cliPath ?? "signal-cli";
@@ -347,6 +352,9 @@ export async function monitorSignalProvider(opts: MonitorSignalOpts = {}): Promi
runtime,
});
void daemonHandle.exited.then((exit) => {
if (daemonStopRequested || opts.abortSignal?.aborted) {
return;
}
daemonExitError = new Error(
`signal daemon exited (code=${String(exit.code ?? "null")} signal=${String(exit.signal ?? "null")})`,
);
@@ -357,7 +365,7 @@ export async function monitorSignalProvider(opts: MonitorSignalOpts = {}): Promi
}
const onAbort = () => {
daemonHandle?.stop();
stopDaemon();
};
opts.abortSignal?.addEventListener("abort", onAbort, { once: true });
@@ -426,6 +434,6 @@ export async function monitorSignalProvider(opts: MonitorSignalOpts = {}): Promi
} finally {
mergedAbort.dispose();
opts.abortSignal?.removeEventListener("abort", onAbort);
daemonHandle?.stop();
stopDaemon();
}
}