fix(config): coerce numeric meta.lastTouchedAt to ISO string

This commit is contained in:
Marcus Castro
2026-02-24 11:12:12 -03:00
committed by Peter Steinberger
parent b5787e4abb
commit 2c4ebf77f3
2 changed files with 85 additions and 1 deletions

View File

@@ -0,0 +1,70 @@
import { describe, expect, it, vi } from "vitest";
describe("meta.lastTouchedAt numeric timestamp coercion", () => {
it("accepts a numeric Unix timestamp and coerces it to an ISO string", async () => {
vi.resetModules();
const { validateConfigObject } = await import("./config.js");
const numericTimestamp = 1770394758161;
const res = validateConfigObject({
meta: {
lastTouchedAt: numericTimestamp,
},
});
expect(res.ok).toBe(true);
if (res.ok) {
expect(typeof res.config.meta?.lastTouchedAt).toBe("string");
expect(res.config.meta?.lastTouchedAt).toBe(new Date(numericTimestamp).toISOString());
}
});
it("still accepts a string ISO timestamp unchanged", async () => {
vi.resetModules();
const { validateConfigObject } = await import("./config.js");
const isoTimestamp = "2026-02-07T01:39:18.161Z";
const res = validateConfigObject({
meta: {
lastTouchedAt: isoTimestamp,
},
});
expect(res.ok).toBe(true);
if (res.ok) {
expect(res.config.meta?.lastTouchedAt).toBe(isoTimestamp);
}
});
it("rejects out-of-range numeric timestamps without throwing", async () => {
vi.resetModules();
const { validateConfigObject } = await import("./config.js");
const res = validateConfigObject({
meta: {
lastTouchedAt: 1e20,
},
});
expect(res.ok).toBe(false);
});
it("passes non-date strings through unchanged (backwards-compatible)", async () => {
vi.resetModules();
const { validateConfigObject } = await import("./config.js");
const res = validateConfigObject({
meta: {
lastTouchedAt: "not-a-date",
},
});
expect(res.ok).toBe(true);
if (res.ok) {
expect(res.config.meta?.lastTouchedAt).toBe("not-a-date");
}
});
it("accepts meta with only lastTouchedVersion (no lastTouchedAt)", async () => {
vi.resetModules();
const { validateConfigObject } = await import("./config.js");
const res = validateConfigObject({
meta: {
lastTouchedVersion: "2026.2.6",
},
});
expect(res.ok).toBe(true);
});
});

View File

@@ -129,7 +129,21 @@ export const OpenClawSchema = z
meta: z
.object({
lastTouchedVersion: z.string().optional(),
lastTouchedAt: z.string().optional(),
// Accept any string unchanged (backwards-compatible) and coerce numeric Unix
// timestamps to ISO strings (agent file edits may write Date.now()).
lastTouchedAt: z
.union([
z.string(),
z.number().transform((n, ctx) => {
const d = new Date(n);
if (Number.isNaN(d.getTime())) {
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Invalid timestamp" });
return z.NEVER;
}
return d.toISOString();
}),
])
.optional(),
})
.strict()
.optional(),