{"record":{"id":"693a33838c14903e","repo":"thedotmack/claude-mem","slug":"projection-error-lease-clock-must-be-a-safe-millisecond","errorCode":null,"errorMessage":"projection_error: lease clock must be a safe millisecond integer","messagePattern":"projection_error: lease clock must be a safe millisecond integer","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"workers/sync-hub/src/do/SyncHub.ts","lineNumber":801,"sourceCode":"\tprivate assertLease(token: string, now: number): void {\n\t\tif (typeof token !== \"string\" || token.length === 0 || this.metaOptional(\"projection_lease_token\") !== token) {\n\t\t\tthrow projectionError(\"projection lease is not held\");\n\t\t}\n\t\tconst expires = this.metaOptional(\"projection_lease_expires_at\");\n\t\tif (!expires || compareCanonicalDecimals(expires, this.leaseNow(now)) <= 0) {\n\t\t\tthrow projectionError(\"projection lease expired\");\n\t\t}\n\t}\n\n\tprivate renewProjectionLease(token: string, now: number): void {\n\t\tthis.ctx.storage.transactionSync(() => {\n\t\t\tthis.assertLease(token, now);\n\t\t\tthis.setMeta(\"projection_lease_expires_at\", this.leaseExpiry(this.leaseNow(now)));\n\t\t});\n\t}\n\n\tprivate leaseNow(now: number): string {\n\t\tif (!Number.isSafeInteger(now) || now < 0) throw projectionError(\"lease clock must be a safe millisecond integer\");\n\t\treturn String(now);\n\t}\n\n\tprivate leaseExpiry(now: string): string {\n\t\treturn (BigInt(assertCanonicalDecimal(now)) + BigInt(PROJECTION_LEASE_MS)).toString(10);\n\t}\n\n\t// ---------------------------------------------------------------------\n\t// Launch safety: physical log compaction is intentionally disabled.\n\t// ---------------------------------------------------------------------\n\n\tasync alarm(): Promise<void> {\n\t\t// A cursor-0 device has no snapshot/reset bootstrap yet, so every\n\t\t// canonical operation remains replayable. Keep the alarm surface for\n\t\t// deployed-object compatibility, but deliberately delete zero rows.\n\t\tawait this.ctx.storage.setAlarm(Date.now() + DAY_MS);\n\t}\n","sourceCodeStart":783,"sourceCodeEnd":819,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d8bc9755e74915e5c3b999181e10a67c889bce2a/workers/sync-hub/src/do/SyncHub.ts#L783-L819","documentation":"projectionError(\"lease clock must be a safe millisecond integer\") is thrown by SyncHub.leaseNow when the 'now' argument supplied for lease bookkeeping is not a non-negative safe integer (Number.isSafeInteger fails or now < 0). Lease timestamps are stored as canonical decimal millisecond strings, so the input clock must be a valid JS safe integer before conversion.","triggerScenarios":"Calling acquire/renew/assertLease paths with now = Date.now() replaced by NaN, undefined (becomes NaN), a float, a negative number, or a value above Number.MAX_SAFE_INTEGER (e.g. microsecond timestamps); passing a string that wasn't parsed.","commonSituations":"Mixing Date objects or microsecond clocks (performance.now()*1000) into a millisecond API; null/undefined 'now' from an unconfigured clock provider in tests; environments where Date.now() is mocked with non-integer values.","solutions":["Pass Date.now() (or another integer millisecond source) as now; validate with Number.isSafeInteger(now) && now >= 0 before the call.","If using a custom clock, ensure it returns integer milliseconds.","Check that a timestamp string/number is parsed with Number() or parseInt before reaching leaseNow.","For sub-millisecond or high-precision clocks, truncate with Math.floor and keep units consistent (ms)."],"exampleFix":"// before\nhub.renewProjectionLease(token, performance.now()); // float ms\n// after\nconst now = Math.floor(Date.now());\nif (Number.isSafeInteger(now) && now >= 0) hub.renewProjectionLease(token, now);","handlingStrategy":"validation","validationCode":"function assertLeaseClock(now: unknown): number {\n  if (typeof now !== \"number\" || !Number.isSafeInteger(now) || now < 0) {\n    throw new TypeError(`lease clock must be a non-negative safe integer (ms), got ${String(now)}`);\n  }\n  return now;\n}\n// call as: hub.renewProjectionLease(token, assertLeaseClock(Date.now()))","typeGuard":"function isMillisTimestamp(v: unknown): v is number {\n  return typeof v === \"number\" && Number.isSafeInteger(v) && v >= 0 && v <= Date.now() + 86400000;\n}","tryCatchPattern":null,"preventionTips":["Always source 'now' from Date.now() or an integer-ms clock abstraction.","Never pass performance.now() (float) or microsecond timestamps to ms-based APIs.","Wrap custom clocks to return Math.floor(integer ms).","Unit-test clock providers to assert safe-integer output."],"tags":["validation","timestamp","integer","clock"],"backgroundTag":"invalid-argument-value","analyzedSha":"d8bc9755e74915e5c3b999181e10a67c889bce2a","analyzedAt":"2026-09-17T16:40:26.182Z","contentChangedAt":"2026-09-17T16:40:26.182Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}