koala73/worldmonitor · error · ConvexError

quietHoursStart must be an integer 0–23

Error message

quietHoursStart must be an integer 0–23

What it means

Thrown by validateQuietHoursArgs when args.quietHoursStart is present but outside [0,23] or is not an integer. quietHoursStart is the hour-of-day (0–23) at which the do-not-disturb window begins. The validator runs early in setQuietHours and setQuietHoursForUser, before any DB read.

Source

Thrown at convex/alertRules.ts:425

  },
});

const QUIET_HOURS_ARGS = {
  variant: v.string(),
  quietHoursEnabled: v.boolean(),
  quietHoursStart: v.optional(v.number()),
  quietHoursEnd: v.optional(v.number()),
  quietHoursTimezone: v.optional(v.string()),
  quietHoursOverride: v.optional(quietHoursOverrideValidator),
} as const;

function validateQuietHoursArgs(args: {
  quietHoursStart?: number;
  quietHoursEnd?: number;
  quietHoursTimezone?: string;
}) {
  if (args.quietHoursStart !== undefined && (args.quietHoursStart < 0 || args.quietHoursStart > 23 || !Number.isInteger(args.quietHoursStart))) {
    throw new ConvexError("quietHoursStart must be an integer 0–23");
  }
  if (args.quietHoursEnd !== undefined && (args.quietHoursEnd < 0 || args.quietHoursEnd > 23 || !Number.isInteger(args.quietHoursEnd))) {
    throw new ConvexError("quietHoursEnd must be an integer 0–23");
  }
  if (args.quietHoursTimezone !== undefined) {
    try {
      Intl.DateTimeFormat(undefined, { timeZone: args.quietHoursTimezone });
    } catch {
      throw new ConvexError("quietHoursTimezone must be a valid IANA timezone (e.g. America/New_York)");
    }
  }
}

export const setQuietHours = mutation({
  args: QUIET_HOURS_ARGS,
  handler: async (ctx, args) => {
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) throw new ConvexError("UNAUTHENTICATED");

View on GitHub (pinned to ffec79ac33)

Solutions

  1. Coerce quietHoursStart to an integer in [0,23] before submitting.
  2. Ensure the picker uses 24-hour 0-based output.
  3. Omit the field if only the end (or enabled flag) is changing.

Example fix

// before
await setQuietHours({ variant, quietHoursEnabled: true, quietHoursStart: '10pm' });
// after
const start = 22; // 10pm in 24-hour 0-based
await setQuietHours({ variant, quietHoursEnabled: true, quietHoursStart: start });
Defensive patterns

Strategy: validation

Validate before calling

function isValidHour(h) {
  return typeof h === 'number' && Number.isInteger(h) && h >= 0 && h <= 23;
}
if (args.quietHoursStart !== undefined && !isValidHour(args.quietHoursStart)) {
  // surface error before submit
}

Type guard

function isQuietHour(n): n is number {
  return typeof n === 'number' && Number.isInteger(n) && n >= 0 && n <= 23;
}

Try / catch

try {
  await setQuietHours(args);
} catch (e) {
  if (e instanceof ConvexError && /quietHoursStart must be an integer/.test(String(e.message))) {
    // coerce start to Math.floor(start) % 24 and retry
  } else throw e;
}

Prevention

When it happens

Trigger: Calling setQuietHours or setQuietHoursForUser with quietHoursStart = 24, -1, 7.5, or any non-integer; supplying a start hour derived from a 1-based or 12-hour picker without normalization.

Common situations: UI time picker emitting 1–24 or AM/PM strings without conversion; passing minutes instead of hours; float arithmetic producing a non-integer.

Related errors


AI-assisted analysis of koala73/worldmonitor@ffec79ac33 (2026-08-12). Data as JSON: /api/errors/f0061d97a5d5714c. Report an issue: GitHub.