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
- Pass an integer 0–23 or omit quietHoursEnd.
- Normalize with ((hour % 24) + 24) % 24 and Math.trunc before sending.
- Remember end < start is valid (window crosses midnight) — do not 'fix' it by adding 24.
- 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
- Use 0 for midnight, never 24.
- Do not 'fix' end < start by adding 24 — overnight windows are intentional.
- Share one hour-validation helper between start and end fields.
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
- quietHoursStart must be an integer 0–23
- digestHour must be an integer 0–23
- quietHoursStart and quietHoursEnd must differ (same value =…
- quietHoursTimezone must be a valid IANA timezone (e.g…
- COMPANY_MONITORING_ADMISSION_QUERY_VERSION_INVALID
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)