koala73/worldmonitor · error · ConvexError

activation session start must be a positive safe integer…

Error message

activation session start must be a positive safe integer within the allowed future clock skew

What it means

ConvexError guard in the Pro activation presentation flow validating the optional sessionStartedAt argument: it must be a positive safe integer not further in the future than MAX_PRO_ACTIVATION_SESSION_FUTURE_SKEW_MS past server time. Fires when the client sends a non-integer, zero/negative, or excessively future-skewed timestamp, rejecting clock-tampered or malformed session claims.

Solutions

  1. Send sessionStartedAt as an integer millisecond epoch from Date.now() on the client
  2. Ensure the client clock is reasonably synced; large future skew is rejected by design
  3. Omit the field if no custom session start timestamp is needed
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at convex/payments/billing.ts:830 when the library encounters an invalid state.

Common situations: See trigger scenarios.


AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-08-21). Data as JSON: /api/errors/57c1728c180bd3c8. Report an issue: GitHub.

Appendix: source

Thrown at convex/payments/billing.ts:846

    // re-create the blind spot this record exists to close.
    if (
      subscription === null ||
      subscription.userId !== userId ||
      !isProActivationPlan(subscription.planKey)
    ) {
      return { status: "not_eligible" as const };
    }

    const now = Date.now();
    if (
      args.sessionStartedAt !== undefined &&
      (
        !Number.isSafeInteger(args.sessionStartedAt) ||
        args.sessionStartedAt <= 0 ||
        args.sessionStartedAt > now + MAX_PRO_ACTIVATION_SESSION_FUTURE_SKEW_MS
      )
    ) {
      throw new ConvexError(
        "activation session start must be a positive safe integer within the allowed future clock skew",
      );
    }

    const existing = await activationPresentationForCohort(ctx, args.activationKey, "day0");
    if (existing === null) {
      await ctx.db.insert("proActivationPresentations", {
        userId,
        subscriptionId: args.activationKey,
        cohort: "day0",
        claimNonce: args.claimNonce,
        claimedAt: now,
        ...(args.sessionStartedAt !== undefined
          ? { sessionStartedAt: args.sessionStartedAt }
          : {}),
        // Day-0 has no confirm handshake, so presentation is recorded here —
        // before the interstitial renders — to keep a subscriber who closes
        // the tab immediately inside the cohort instead of invisible.

View on GitHub (pinned to 7d06c8633d)