koala73/worldmonitor · error · ConvexError

quietHoursEnd must be an integer 0–23

Error message

quietHoursEnd must be an integer 0–23

What it means

The second check in validateQuietHoursArgs (convex/alertRules.ts:427): when quietHoursEnd is supplied it must be an integer in 0–23, else setQuietHours / setQuietHoursForUser throw ConvexError 'quietHoursEnd must be an integer 0–23'. quietHoursEnd may legitimately be smaller than quietHoursStart (an overnight window like 22→7), so only integer-ness and range matter here.

Solutions

  1. Pass an integer 0–23 or omit quietHoursEnd.
  2. Normalize with ((hour % 24) + 24) % 24 and Math.trunc before sending.
  3. Remember end < start is valid (window crosses midnight) — do not 'fix' it by adding 24.
  4. Mirror the integer/range check client-side for inline validation.

Example fix

// before
await mutateAPI.alertRules.setQuietHours({ variant: 'default', quietHoursEnabled: true, quietHoursStart: 22, quietHoursEnd: 24 });
// ConvexError: quietHoursEnd must be an integer 0–23

// after — 24:00 means midnight = 0
await mutateAPI.alertRules.setQuietHours({ variant: 'default', quietHoursEnabled: true, quietHoursStart: 22, quietHoursEnd: 0 });
Defensive patterns

Strategy: validation

Validate before calling

const isValidHour = (h: unknown): h is number =>
  typeof h === 'number' && Number.isInteger(h) && h >= 0 && h <= 23;

if (quietHoursEnd !== undefined && !isValidHour(quietHoursEnd)) {
  setFieldError('quietHoursEnd', 'Pick a whole hour between 0 and 23');
  return;
}
await mutateAPI.alertRules.setQuietHours({ variant, quietHoursEnabled, quietHoursStart, quietHoursEnd });

Type guard

const isValidQuietHour = (h: unknown): h is number =>
  typeof h === 'number' && Number.isInteger(h) && h >= 0 && h <= 23;

Try / catch

try {
  await mutateAPI.alertRules.setQuietHours(args);
} catch (err) {
  if (err instanceof ConvexError && err.data === 'quietHoursEnd must be an integer 0–23') {
    setFieldError('quietHoursEnd', 'Pick a whole hour between 0 and 23');
    return;
  }
  throw err;
}

Prevention

When it happens

Trigger: setQuietHours (or setQuietHoursForUser) with quietHoursEnd: 7.5, a negative value, 24, or NaN — typically from fractional pickers, timezone offset math, or '24:00'-style conventions.

Common situations: Same sources as quietHoursStart: float sliders, overnight arithmetic wrapping incorrectly, imported 1–24 hour systems, string parses of '7:30'.

Related errors


AI-assisted analysis of koala73/worldmonitor@eeab0a219f (2026-08-21). Data as JSON: /api/errors/3b60bebcee8e2cd6. Report an issue: GitHub.

Appendix: source

Thrown at convex/alertRules.ts:428

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");
    const userId = identity.subject;
    await assertProEntitlement(ctx, userId);
    validateQuietHoursArgs(args);

View on GitHub (pinned to eeab0a219f)