koala73/worldmonitor · error · Error

[initRamp] seedLastWaveBroadcastId and seedLastWaveSentAt mu

Error message

[initRamp] seedLastWaveBroadcastId and seedLastWaveSentAt must be provided together.

What it means

Thrown by initRamp when exactly one of seedLastWaveBroadcastId and seedLastWaveSentAt is provided. These two fields are a required pair because the kill-gate logic needs both the broadcast id (to fetch bounce/complaint stats) and the sent-at timestamp (to scope the stats window). Providing only one yields an inconsistent seed state.

Source

Thrown at convex/broadcast/rampRunner.ts:175

    // 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();
    if (existing) {
      throw new Error(
        `[initRamp] ramp already configured (active=${existing.active}, tier=${existing.currentTier}). Run abortRamp first if reconfiguring.`,
      );
    }
    await ctx.db.insert("broadcastRampConfig", {

View on GitHub (pinned to ffec79ac33)

Solutions

  1. Provide both seedLastWaveBroadcastId AND seedLastWaveSentAt together, or omit both entirely.
  2. Look up the prior wave's record to obtain both values consistently.

Example fix

// before
await initRamp(ctx, {
  rampCurve: [100, 250], waveLabelPrefix: "w",
  seedLastWaveBroadcastId: "abc", // seedLastWaveSentAt missing
});
// after — provide both (or neither)
await initRamp(ctx, {
  rampCurve: [100, 250], waveLabelPrefix: "w",
  seedLastWaveBroadcastId: "abc",
  seedLastWaveSentAt: Date.parse("2024-10-15T12:00:00Z"),
});
Defensive patterns

Strategy: validation

Validate before calling

const hasBroadcast = !!args.seedLastWaveBroadcastId;
const hasSentAt = typeof args.seedLastWaveSentAt === "number";
if (hasBroadcast !== hasSentAt) {
  throw new Error("Provide both seedLastWaveBroadcastId and seedLastWaveSentAt, or neither");
}
await initRamp(ctx, args);

Prevention

When it happens

Trigger: Calling initRamp with seedLastWaveBroadcastId set but seedLastWaveSentAt omitted (or vice versa); a config builder that populated one field from a prior-wave record but forgot the other.

Common situations: Operator manually filled in the broadcast id from the Resend dashboard but didn't look up the sent timestamp; a script pulled fields from an incomplete prior-wave record.

Related errors


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