koala73/worldmonitor · warning · Error

[assignAndExportWave] pool empty — all registrations are sup

Error message

[assignAndExportWave] pool empty — all registrations are suppressed/paid/already-stamped. Nothing to send.

What it means

Thrown by assignAndExportWave when the reservoir picked zero eligible emails after streaming all registrations. Every candidate was filtered out because it was suppressed, paid, or already stamped with a proLaunchWave. This signals the waitlist is drained for this ramp tier and there is nothing to send.

Source

Thrown at convex/broadcast/audienceWaveExport.ts:350

        { cursor, numItems: REGISTRATIONS_PAGE_SIZE },
      );
      for (const row of page.page) {
        const email = row.normalizedEmail;
        if (!email || email.length === 0) continue;
        if (suppressedSet.has(email)) continue;
        if (paidSet.has(email)) continue;
        if (row.proLaunchWave) continue;
        reservoir.offer(email);
      }
      if (page.isDone) break;
      cursor = page.continueCursor;
    }

    const picked = reservoir.values();
    const poolSize = reservoir.totalSeen();

    if (picked.length === 0) {
      throw new Error(
        `[assignAndExportWave] pool empty — all registrations are suppressed/paid/already-stamped. Nothing to send.`,
      );
    }

    // Step 3: create the Resend segment FIRST so we never stamp a
    // contact until we know it has a destination to land in. If
    // segment creation fails, the picked rows are still unstamped and
    // remain available for the next wave's pick — no data loss, no
    // stranded contacts.
    const segmentName = `pro-launch-${waveLabel}`;
    const segmentId = await createSegment(apiKey, segmentName);

    // Step 4: push picked contacts to the segment, then stamp ONLY on
    // successful push outcomes (created / linkedExisting /
    // alreadyInSegment). This ordering is load-bearing — see the
    // file docstring's "Atomicity" section.
    //
    //   - push succeeds  → stamp Convex → contact won't be re-picked

View on GitHub (pinned to ffec79ac33)

Solutions

  1. Treat this as end-of-ramp — reduce or stop further wave scheduling for this audience.
  2. Inspect the suppression list and paid set sizes to confirm the pool is genuinely exhausted (not a misconfiguration).
  3. If suppression is unexpectedly large, audit the suppression source for an over-broad rule.
  4. Do not retry with the same parameters — the pool state will not change without new registrations.

Example fix

// before
await assignAndExportWave(ctx, { waveLabel: "wave-99", count: 500 }); // pool empty
// after — guard before calling; check remaining pool size
const remaining = await ctx.runQuery(internal.broadcast.audienceWaveExport._countEligible, {});
if (remaining === 0) {
  console.log("Waitlist exhausted — ramp complete.");
  return;
}
Defensive patterns

Strategy: validation

Validate before calling

// Pre-check eligible pool size before exporting
const remaining = await ctx.runQuery(internal.broadcast.audienceWaveExport._countEligible, {});
if (remaining === 0) {
  console.log("Waitlist exhausted — ramp complete.");
  return;
}
await assignAndExportWave(ctx, { waveLabel, count });

Try / catch

try {
  await assignAndExportWave(ctx, { waveLabel, count });
} catch (e) {
  if (e.message.includes("pool empty")) {
    // end of ramp — stop scheduling further waves
  } else throw e;
}

Prevention

When it happens

Trigger: All registrations are in the suppressed set, the paid set, or already have proLaunchWave set; the waitlist has been fully processed for prior waves; suppression list grew to cover the entire remaining pool.

Common situations: End of a launch ramp — all registrants already received their wave; an over-broad suppression rule filtered everyone; paid users dominate the small remaining pool; a prior wave stamped the last unstamped rows.

Related errors


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