koala73/worldmonitor · error · ConvexError
quietHoursStart must be an integer 0–23
Error message
quietHoursStart must be an integer 0–23
What it means
validateQuietHoursArgs (convex/alertRules.ts:419) runs for both setQuietHours and setQuietHoursForUser. When quietHoursStart is supplied it must be an integer in 0–23 (Number.isInteger plus bounds check); otherwise the mutation throws ConvexError 'quietHoursStart must be an integer 0–23' (string data, no code). The arg is v.optional(v.number()), so non-numeric values fail Convex's own validator first.
Solutions
- Pass an integer 0–23 or omit quietHoursStart.
- Wrap computed values: ((hour % 24) + 24) % 24 and Math.trunc fractional input.
- Build the picker options from 0–23 so out-of-range values cannot be selected.
- Validate client-side with the same integer/range predicate before calling the mutation.
Example fix
// before
await mutateAPI.alertRules.setQuietHours({ variant: 'default', quietHoursEnabled: true, quietHoursStart: 22.5, quietHoursEnd: 7 });
// ConvexError: quietHoursStart must be an integer 0–23
// after
const start = ((Math.trunc(pickedStart) % 24) + 24) % 24;
await mutateAPI.alertRules.setQuietHours({ variant: 'default', quietHoursEnabled: true, quietHoursStart: start, quietHoursEnd: 7 }); 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 (quietHoursStart !== undefined && !isValidHour(quietHoursStart)) {
setFieldError('quietHoursStart', '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 === 'quietHoursStart must be an integer 0–23') {
setFieldError('quietHoursStart', 'Pick a whole hour between 0 and 23');
return;
}
throw err;
} Prevention
- Populate quiet-hour selects with 0–23 only.
- Wrap offset math for overnight windows with modulo so values stay in range.
- Truncate fractional slider output before submission.
- Remember end < start is legal (crosses midnight) — only integer-ness and range are checked.
When it happens
Trigger: setQuietHours (or setQuietHoursForUser via runMutation) with quietHoursStart: 9.5 from a fractional time picker; negative values from a TZ conversion; 24 from a '24:00' convention; a computed NaN forwarded unchecked.
Common situations: Quiet-hours sliders emitting floats; overnight-window math (start 22 minus offset) going negative; imported notification settings from systems using 1–24 hours; UI selects built from '00:00'–'24:00' strings.
Related errors
- quietHoursEnd 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/f0061d97a5d5714c.
Report an issue: GitHub.
Appendix: 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 eeab0a219f)