diff --git a/src/infra/heartbeat-runner-execution.ts b/src/infra/heartbeat-runner-execution.ts index afd71e19a219..0e3d868f47ff 100644 --- a/src/infra/heartbeat-runner-execution.ts +++ b/src/infra/heartbeat-runner-execution.ts @@ -184,6 +184,14 @@ export async function resolveHeartbeatWakeStage(opts: HeartbeatRunOptions) { wakeSource !== "cron" && !isWithinActiveHours(cfg, heartbeat, startedAt) ) { + // Documented observable skip (`system heartbeat last` / troubleshooting + // docs promise reason=quiet-hours); every sibling skip past this point + // emits, so a silent return here hides the window from operators. + emitHeartbeatEvent({ + status: "skipped", + reason: "quiet-hours", + durationMs: Date.now() - startedAt, + }); return { kind: "skipped", reason: "quiet-hours" } as const; } diff --git a/src/infra/heartbeat-runner.returns-default-unset.test.ts b/src/infra/heartbeat-runner.returns-default-unset.test.ts index 4cb0a858ed55..7e58af8a909f 100644 --- a/src/infra/heartbeat-runner.returns-default-unset.test.ts +++ b/src/infra/heartbeat-runner.returns-default-unset.test.ts @@ -23,6 +23,7 @@ import { } from "../test-utils/channel-plugins.js"; import { typedCases } from "../test-utils/typed-cases.js"; import { normalizeSessionDeliveryState } from "../utils/delivery-context.shared.js"; +import { getLastHeartbeatEvent, resetHeartbeatEventsForTest } from "./heartbeat-events.js"; import { type HeartbeatDeps, isHeartbeatEnabledForAgent, @@ -337,6 +338,7 @@ beforeAll(async () => { beforeEach(() => { resetSystemEventsForTest(); + resetHeartbeatEventsForTest(); if (testRegistry) { setActivePluginRegistry(testRegistry); } @@ -862,6 +864,8 @@ describe("runHeartbeatOnce", () => { if (res.status === "skipped") { expect(res.reason).toBe("quiet-hours"); } + // Documented observable skip: `system heartbeat last` must show the window. + expect(getLastHeartbeatEvent()).toMatchObject({ status: "skipped", reason: "quiet-hours" }); }); it("skips a routeless interval poll before the agent run", async () => {