koala73/worldmonitor · error · Error

[recoverFromPartialFailure:manual-finished] currentTier=${ro

Error message

[recoverFromPartialFailure:manual-finished] currentTier=${row.currentTier} would advance past rampCurve.length=${row.rampCurve.length}. Curve is complete; nothing to recover.

What it means

In the 'manual-finished' recovery branch, recoverFromPartialFailure computes nextTier = currentTier + 1 to advance the ramp. If nextTier is already >= rampCurve.length, the ramp curve is fully consumed and there is no further tier to advance to — recovering would write an out-of-bounds tier. The handler throws rather than silently no-op'ing or corrupting the pointer.

Source

Thrown at convex/broadcast/rampRunner.ts:399

      pendingRunId: undefined,
      pendingRunStartedAt: undefined,
      pendingWaveLabel: undefined,
      pendingSegmentId: undefined,
      pendingAssigned: undefined,
      pendingExportAt: undefined,
      pendingBroadcastId: undefined,
      pendingBroadcastAt: undefined,
    } as const;

    if (args.recovery === "manual-finished") {
      // Resolve each field: operator-supplied wins, persisted pending* is the
      // fallback. sentAt is operator-only (no fallback).
      const broadcastId = args.broadcastId ?? row.pendingBroadcastId;
      const segmentId = args.segmentId ?? row.pendingSegmentId;
      const assigned = args.assigned ?? row.pendingAssigned;
      const nextTier = row.currentTier + 1;
      if (nextTier >= row.rampCurve.length) {
        throw new Error(
          `[recoverFromPartialFailure:manual-finished] currentTier=${row.currentTier} would advance past rampCurve.length=${row.rampCurve.length}. Curve is complete; nothing to recover.`,
        );
      }
      const waveLabel =
        args.waveLabel ??
        row.pendingWaveLabel ??
        `${row.waveLabelPrefix}-${nextTier + row.waveLabelOffset}`;

      const missing: string[] = [];
      if (!broadcastId) missing.push("broadcastId");
      if (!segmentId) missing.push("segmentId");
      if (assigned === undefined) missing.push("assigned");
      if (args.sentAt === undefined) missing.push("sentAt");
      if (missing.length > 0) {
        throw new Error(
          `[recoverFromPartialFailure:manual-finished] missing required field(s): ${missing.join(", ")}. ` +
            `Operator must supply (or rely on persisted pending* state from a prior runner). Persisted state: ` +
            `pendingBroadcastId=${row.pendingBroadcastId ?? "-"}, pendingSegmentId=${row.pendingSegmentId ?? "-"}, ` +

View on GitHub (pinned to ffec79ac33)

Solutions

  1. Treat the ramp as complete: if the wave genuinely sent, record it directly (lastWave* fields) rather than advancing the tier, or run abortRamp.
  2. Verify currentTier against rampCurve.length via getRampStatus before calling recovery.
  3. If more waves are genuinely needed, abortRamp and initRamp with an extended rampCurve + correct waveLabelOffset/seed fields.

Example fix

// before
await ctx.runMutation(internal.broadcast.rampRunner.recoverFromPartialFailure, { recovery: 'manual-finished', reason });

// after
const s = await ctx.runQuery(internal.broadcast.rampRunner.getRampStatus, {});
if (s.configured && s.currentTier + 1 >= s.rampCurve.length) {
  throw new Error('Ramp curve complete — nothing to advance. Record manually or abortRamp.');
}
await ctx.runMutation(internal.broadcast.rampRunner.recoverFromPartialFailure, { recovery: 'manual-finished', reason });
Defensive patterns

Strategy: validation

Validate before calling

// Before manual-finished recovery, ensure the curve has room to advance
const s = await ctx.runQuery(internal.broadcast.rampRunner.getRampStatus, {});
if (s.configured && s.currentTier + 1 >= s.rampCurve.length) {
  throw new Error('Ramp curve complete — nothing to recover/advance. Record manually or abortRamp.');
}

Type guard

function curveHasRoom(s: { configured: boolean; currentTier?: number; rampCurve?: number[] }): boolean {
  return s.configured === true && typeof s.currentTier === 'number' && Array.isArray(s.rampCurve) && s.currentTier + 1 < s.rampCurve.length;
}

Prevention

When it happens

Trigger: Calling recoverFromPartialFailure({recovery:'manual-finished'}) when the last partial failure occurred on the final tier of the rampCurve and currentTier+1 would exceed the curve length.

Common situations: The final wave partially failed; the operator manually confirmed it sent and tries to mark it finished, but the curve has no room to advance; the ramp already reached completion elsewhere.

Related errors


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