From 61be533ad4ede0a30f772d2ff6ab270a8b320ea9 Mon Sep 17 00:00:00 2001 From: velamints2 Date: Tue, 3 Mar 2026 03:22:09 +0800 Subject: [PATCH] fix(restart): deduplicate reason line in restart sentinel message When gateway.restart is triggered with a reason but no separate note, the payload sets both message and stats.reason to the same text. formatRestartSentinelMessage() then emits both the message line and a redundant 'Reason: ' line, doubling the restart reason in the notification delivered to the agent session. Skip the 'Reason:' line when stats.reason matches the already-emitted message text. Add regression tests for both duplicate and distinct reason scenarios. --- src/infra/restart-sentinel.test.ts | 30 ++++++++++++++++++++++++++++++ src/infra/restart-sentinel.ts | 2 +- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/infra/restart-sentinel.test.ts b/src/infra/restart-sentinel.test.ts index ec97c8c5c..76b9e53b5 100644 --- a/src/infra/restart-sentinel.test.ts +++ b/src/infra/restart-sentinel.test.ts @@ -116,3 +116,33 @@ describe("restart sentinel", () => { expect(textA).not.toContain('"ts"'); }); }); + +describe("restart sentinel message dedup", () => { + it("omits duplicate Reason: line when stats.reason matches message", () => { + const payload = { + kind: "restart" as const, + status: "ok" as const, + ts: Date.now(), + message: "Applying config changes", + stats: { mode: "gateway.restart", reason: "Applying config changes" }, + }; + const result = formatRestartSentinelMessage(payload); + // The message text should appear exactly once, not duplicated as "Reason: ..." + const occurrences = result.split("Applying config changes").length - 1; + expect(occurrences).toBe(1); + expect(result).not.toContain("Reason:"); + }); + + it("keeps Reason: line when stats.reason differs from message", () => { + const payload = { + kind: "restart" as const, + status: "ok" as const, + ts: Date.now(), + message: "Restart requested by /restart", + stats: { mode: "gateway.restart", reason: "/restart" }, + }; + const result = formatRestartSentinelMessage(payload); + expect(result).toContain("Restart requested by /restart"); + expect(result).toContain("Reason: /restart"); + }); +}); diff --git a/src/infra/restart-sentinel.ts b/src/infra/restart-sentinel.ts index 919fb56a3..baf816804 100644 --- a/src/infra/restart-sentinel.ts +++ b/src/infra/restart-sentinel.ts @@ -118,7 +118,7 @@ export function formatRestartSentinelMessage(payload: RestartSentinelPayload): s lines.push(message); } const reason = payload.stats?.reason?.trim(); - if (reason) { + if (reason && reason !== message) { lines.push(`Reason: ${reason}`); } if (payload.doctorHint?.trim()) {