koala73/worldmonitor · error · Error
[initRamp] rampCurve entries must be positive integers
Error message
[initRamp] rampCurve entries must be positive integers
What it means
Thrown by initRamp when any entry in args.rampCurve fails Number.isInteger or is <= 0. Each curve entry is a per-wave recipient count and must be a positive integer — fractional, zero, or negative counts are meaningless for audience sizing and would break the reservoir-sampling math downstream.
Source
Thrown at convex/broadcast/rampRunner.ts:169
seedLastWaveBroadcastId: v.optional(v.string()),
seedLastWaveSentAt: v.optional(v.number()),
seedLastWaveLabel: v.optional(v.string()),
seedLastWaveSegmentId: v.optional(v.string()),
seedLastWaveAssigned: v.optional(v.number()),
// Locale filter — when true, pickWaveAction excludes contacts with
// non-English locale (canonical via `users.localePrimary`, fallback
// via email-TLD heuristic for legacy waitlist registrants without a
// users row). Default false to preserve byte-identical behavior for
// existing ramps. Operators opt in for wave-8+ deliberately. Run
// `_dryRunNonEnglishExclusion` first to inspect impact before flipping.
excludeNonEnglish: v.optional(v.boolean()),
},
handler: async (ctx, args) => {
if (args.rampCurve.length === 0) {
throw new Error("[initRamp] rampCurve must be non-empty");
}
if (args.rampCurve.some((n) => !Number.isInteger(n) || n <= 0)) {
throw new Error("[initRamp] rampCurve entries must be positive integers");
}
const offset = args.waveLabelOffset ?? 0;
const hasSeedBroadcast = !!args.seedLastWaveBroadcastId;
const hasSeedSentAt = typeof args.seedLastWaveSentAt === "number";
if (hasSeedBroadcast !== hasSeedSentAt) {
throw new Error(
"[initRamp] seedLastWaveBroadcastId and seedLastWaveSentAt must be provided together.",
);
}
if (offset > 0 && !hasSeedBroadcast) {
throw new Error(
`[initRamp] waveLabelOffset=${offset} signals resumption after manual waves; seedLastWaveBroadcastId + seedLastWaveSentAt are required so the first cron tick can apply the kill-gate against the prior wave. Pass them, or set waveLabelOffset=0 to start a fresh ramp.`,
);
}
const existing = await ctx.db
.query("broadcastRampConfig")
.withIndex("by_key", (q) => q.eq("key", RAMP_KEY))
.first();View on GitHub (pinned to ffec79ac33)
Solutions
- Ensure every entry in rampCurve is a positive integer (>= 1).
- Round/ceil computed counts to integers and clamp to a minimum of 1 before passing.
- Validate the curve with a pre-check: curve.every(n => Number.isInteger(n) && n > 0).
Example fix
// before
await initRamp(ctx, { rampCurve: [100, 0, 500.5], waveLabelPrefix: "w" });
// after
await initRamp(ctx, { rampCurve: [100, 50, 501], waveLabelPrefix: "w" }); Defensive patterns
Strategy: validation
Validate before calling
if (!rampCurve.every(n => Number.isInteger(n) && n > 0)) {
throw new Error("rampCurve entries must be positive integers");
}
await initRamp(ctx, { rampCurve, waveLabelPrefix }); Type guard
function isPositiveIntegerCurve(c: unknown): c is number[] {
return Array.isArray(c) && c.every(n => Number.isInteger(n) && (n as number) > 0);
} Prevention
- Clamp/ceil computed curve values to positive integers.
- Validate the curve in a config-lint step.
- Never allow null/0 placeholders in a production ramp curve.
When it happens
Trigger: Calling initRamp with rampCurve containing a zero (e.g. [100, 0, 500]), a negative number, a fractional value like 100.5, or a value that is not a number (NaN coerced).
Common situations: Curve read from a JSON config that had a null/0 placeholder; a computed curve where a percentage multiplied down to a fraction; a templating bug inserted a non-numeric value.
Related errors
- [initRamp] rampCurve must be non-empty
- [initRamp] seedLastWaveBroadcastId and seedLastWaveSentAt mu
- [initRamp] waveLabelOffset=${offset} signals resumption afte
- INVALID_NAME
- INVALID_PREFIX
AI-assisted analysis of koala73/worldmonitor@ffec79ac33 (2026-08-12).
Data as JSON: /api/errors/4c0e109478563872.
Report an issue: GitHub.