koala73/worldmonitor · error · Error

[_recordWaveSent] no ramp configured

Error message

[_recordWaveSent] no ramp configured

What it means

_recordWaveSent is the terminal success commit: it advances currentTier and records lastWave* metadata after a wave is sent. It loads the config via loadConfig and throws if no row exists, because committing a wave against an aborted ramp would be inconsistent.

Source

Thrown at convex/broadcast/rampRunner.ts:782

/**
 * Internal mutation that the action calls to atomically advance the tier +
 * record a successful wave-send. Validates that the lease still belongs to
 * this runId AND clears all pending-progress markers.
 */
export const _recordWaveSent = internalMutation({
  args: {
    runId: v.string(),
    expectedCurrentTier: v.number(),
    newTier: v.number(),
    waveLabel: v.string(),
    broadcastId: v.string(),
    segmentId: v.string(),
    assigned: v.number(),
    sentAt: v.number(),
  },
  handler: async (ctx, args) => {
    const row = await loadConfig(ctx);
    if (!row) throw new Error("[_recordWaveSent] no ramp configured");
    if (row.currentTier !== args.expectedCurrentTier) {
      throw new Error(
        `[_recordWaveSent] tier moved underneath us: expected ${args.expectedCurrentTier}, found ${row.currentTier}. Refusing to overwrite.`,
      );
    }
    if (row.pendingRunId !== args.runId) {
      // The lease changed under us — operator force-released it, or
      // recoverFromPartialFailure cleared it. We must NOT advance the tier;
      // bubble to Convex auto-Sentry so ops can investigate.
      throw new Error(
        `[_recordWaveSent] lease lost: expected runId=${args.runId}, found ${row.pendingRunId ?? "<cleared>"}. Refusing to advance tier — investigate what cleared the lease.`,
      );
    }
    await ctx.db.patch(row._id, {
      currentTier: args.newTier,
      lastWaveLabel: args.waveLabel,
      lastWaveBroadcastId: args.broadcastId,
      lastWaveSegmentId: args.segmentId,

View on GitHub (pinned to ffec79ac33)

Solutions

  1. Do not retry — the config is gone by intent. Surface to Sentry; verify whether the Resend broadcast actually sent.
  2. If accidental abort, re-seed via initRamp with the correct currentTier/seed so the next wave is the right one (avoid double-sending the just-sent wave).
  3. Serialize operator aborts against live runs.
Defensive patterns

Strategy: try-catch

Validate before calling

// Runner-side: re-check config existence before the terminal commit
const row = await ctx.db.query('broadcastRampConfig').withIndex('by_key', (q) => q.eq('key', 'current')).first();
if (!row) {
  return { aborted: 'config-gone' }; // a broadcast may have sent — reconcile manually
}

Try / catch

try {
  await ctx.runMutation(internal.broadcast.rampRunner._recordWaveSent, { runId, expectedCurrentTier, newTier, ... });
} catch (e) {
  console.error('[runner] _recordWaveSent failed:', (e as Error).message);
  throw e; // ops must verify whether the Resend broadcast actually sent
}

Prevention

When it happens

Trigger: The runner reached the final commit step but abortRamp deleted the config between run start and _recordWaveSent; or the run executes in an unseeded deployment.

Common situations: Operator aborted during an in-flight send; deployment mismatch; parallel recovery cleared the config.

Related errors


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