koala73/worldmonitor · error · Error

[initRamp] rampCurve must be non-empty

Error message

[initRamp] rampCurve must be non-empty

What it means

Thrown by initRamp when args.rampCurve is an empty array. The ramp curve defines the per-wave recipient counts that escalate the broadcast, and an empty curve means the ramp has no tiers to progress through — the config would be a no-op. This is the first validation check in initRamp.

Source

Thrown at convex/broadcast/rampRunner.ts:166

    // Required as a pair when `waveLabelOffset > 0` (operational
    // signal that the ramp is resuming after manual waves). The very
    // first wave ever (offset=0) is exempt because there is no prior.
    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

View on GitHub (pinned to ffec79ac33)

Solutions

  1. Provide a non-empty rampCurve, e.g. [100, 250, 500, 1000] escalating per wave.
  2. Validate the curve is non-empty in the ramp-config script before calling initRamp.
  3. Confirm the config source actually contains the intended curve values.

Example fix

// before
await initRamp(ctx, { rampCurve: [], waveLabelPrefix: "w" });
// after
await initRamp(ctx, { rampCurve: [100, 250, 500, 1000], waveLabelPrefix: "w" });
Defensive patterns

Strategy: validation

Validate before calling

if (!Array.isArray(rampCurve) || rampCurve.length === 0) {
  throw new Error("rampCurve must be a non-empty array");
}
await initRamp(ctx, { rampCurve, waveLabelPrefix });

Type guard

function isNonEmptyCurve(c: unknown): c is number[] {
  return Array.isArray(c) && c.length > 0;
}

Prevention

When it happens

Trigger: Calling initRamp with rampCurve: []; passing an array that was dynamically constructed and ended up empty (e.g. a generator that produced no entries); a config typo where the curve was omitted/forgotten.

Common situations: A ramp-config script defaulted the curve to [] and wasn't filled in; the curve was read from a config file with a missing/empty rampCurve key; test invocation forgot to populate the curve.

Related errors


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