From 0840fea50d18e5437f13557b5e0313d2ed2f020a Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 30 May 2026 08:38:06 -0400 Subject: [PATCH] fix(matrix): guard startup verification timestamps --- .../monitor/startup-verification.test.ts | 27 ++++++++++++++++++- .../matrix/monitor/startup-verification.ts | 14 ++++++++-- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/extensions/matrix/src/matrix/monitor/startup-verification.test.ts b/extensions/matrix/src/matrix/monitor/startup-verification.test.ts index 88a53106287..8103705cbeb 100644 --- a/extensions/matrix/src/matrix/monitor/startup-verification.test.ts +++ b/extensions/matrix/src/matrix/monitor/startup-verification.test.ts @@ -1,7 +1,7 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; -import { describe, expect, it, vi } from "vitest"; +import { afterEach, describe, expect, it, vi } from "vitest"; import { ensureMatrixStartupVerification } from "./startup-verification.js"; function createTempStateDir(): string { @@ -80,6 +80,10 @@ function createHarness(params?: { } describe("ensureMatrixStartupVerification", () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + it("skips automatic requests when the device is already verified", async () => { const tempHome = createTempStateDir(); const harness = createHarness({ verified: true }); @@ -203,6 +207,27 @@ describe("ensureMatrixStartupVerification", () => { expect(fs.existsSync(createStateFilePath(tempHome))).toBe(true); }); + it("falls back when startup verification nowMs is outside Date range", async () => { + vi.spyOn(Date, "now").mockReturnValue(Date.parse("2026-05-30T12:00:00.000Z")); + const tempHome = createTempStateDir(); + const stateFilePath = createStateFilePath(tempHome); + const harness = createHarness(); + + const result = await ensureMatrixStartupVerification({ + client: harness.client as never, + auth: createAuth(), + accountConfig: {}, + stateFilePath, + nowMs: 8_640_000_000_000_001, + }); + + expect(result.kind).toBe("requested"); + const state = JSON.parse(fs.readFileSync(stateFilePath, "utf-8")) as { + attemptedAt?: string; + }; + expect(state.attemptedAt).toBe("2026-05-30T12:00:00.000Z"); + }); + it("keeps startup verification failures non-fatal", async () => { const tempHome = createTempStateDir(); const harness = createHarness({ diff --git a/extensions/matrix/src/matrix/monitor/startup-verification.ts b/extensions/matrix/src/matrix/monitor/startup-verification.ts index 0876da8ccac..af5c60a159f 100644 --- a/extensions/matrix/src/matrix/monitor/startup-verification.ts +++ b/extensions/matrix/src/matrix/monitor/startup-verification.ts @@ -1,6 +1,7 @@ import fs from "node:fs/promises"; import path from "node:path"; import { readJsonFileWithFallback, writeJsonFileAtomically } from "openclaw/plugin-sdk/json-store"; +import { timestampMsToIsoString } from "openclaw/plugin-sdk/number-runtime"; import type { MatrixConfig } from "../../types.js"; import { resolveMatrixStoragePaths } from "../client/storage.js"; import type { MatrixAuth } from "../client/types.js"; @@ -95,6 +96,14 @@ function resolveRetryAfterMs(params: { return remaining > 0 ? remaining : undefined; } +function resolveStartupVerificationTimestamp(nowMs: unknown): string { + return ( + timestampMsToIsoString(nowMs) ?? + timestampMsToIsoString(Date.now()) ?? + "1970-01-01T00:00:00.000Z" + ); +} + function shouldHonorCooldown(params: { state: MatrixStartupVerificationState | null; verification: MatrixOwnDeviceVerificationStatus; @@ -189,6 +198,7 @@ export async function ensureMatrixStartupVerification(params: { ); const cooldownMs = cooldownHours * 60 * 60 * 1000; const nowMs = params.nowMs ?? Date.now(); + const attemptedAt = resolveStartupVerificationTimestamp(nowMs); const state = await readStartupVerificationState(statePath); const stateCooldownMs = resolveStateCooldownMs(state, cooldownMs); if (shouldHonorCooldown({ state, verification, stateCooldownMs, nowMs })) { @@ -208,7 +218,7 @@ export async function ensureMatrixStartupVerification(params: { await writeJsonFileAtomically(statePath, { userId: verification.userId, deviceId: verification.deviceId, - attemptedAt: new Date(nowMs).toISOString(), + attemptedAt, outcome: "requested", requestId: request.id, transactionId: request.transactionId, @@ -224,7 +234,7 @@ export async function ensureMatrixStartupVerification(params: { await writeJsonFileAtomically(statePath, { userId: verification.userId, deviceId: verification.deviceId, - attemptedAt: new Date(nowMs).toISOString(), + attemptedAt, outcome: "failed", error, } satisfies MatrixStartupVerificationState).catch(() => {});