koala73/worldmonitor · error · Error

[forceReleaseLease] no ramp configured

Error message

[forceReleaseLease] no ramp configured

What it means

forceReleaseLease is the operator escape hatch that clears a stuck pendingRunId lease and marks the run partial-failure for later recovery. It loads the config via loadConfig and throws if no row exists, so releasing a lease on a non-existent ramp cannot succeed silently.

Source

Thrown at convex/broadcast/rampRunner.ts:670

/**
 * Operator-only last-resort lease release.
 *
 * Use ONLY when a cron action is genuinely wedged (process died silently
 * between claim and any catch block, leaving a held lease with no
 * partial-failure status). Investigate Convex action logs + Resend dashboard
 * BEFORE calling this — the side effects might have actually completed
 * (segment created, broadcast sent) and the right recovery is then
 * `recoverFromPartialFailure({recovery:"manual-finished"})`, not a fresh send.
 *
 * Sets `lastRunStatus = "partial-failure"` so `recoverFromPartialFailure`
 * picks up; preserves any `pending*` progress markers so the operator can
 * decide between manual-finished and discard-and-rotate from persisted state.
 */
export const forceReleaseLease = internalMutation({
  args: { reason: v.string() },
  handler: async (ctx, { reason }) => {
    const row = await loadConfig(ctx);
    if (!row) throw new Error("[forceReleaseLease] no ramp configured");
    if (!row.pendingRunId) {
      return {
        ok: true as const,
        noop: true as const,
        currentStatus: row.lastRunStatus,
      };
    }
    const releasedRunId = row.pendingRunId;
    const heldForMs =
      row.pendingRunStartedAt !== undefined
        ? Date.now() - row.pendingRunStartedAt
        : undefined;
    await ctx.db.patch(row._id, {
      pendingRunId: undefined,
      pendingRunStartedAt: undefined,
      lastRunStatus: "partial-failure",
      lastRunAt: Date.now(),
      lastRunError: `forced-release: ${reason.slice(0, 200)} (was held by ${releasedRunId}${heldForMs !== undefined ? `, age ${Math.round(heldForMs / 1000)}s` : ""})`,

View on GitHub (pinned to ffec79ac33)

Solutions

  1. Preflight with getRampStatus; if {configured:false} there is no lease to release.
  2. If abortRamp ran, the lease was deleted with the row — no action needed.
  3. Re-seed via initRamp if a new ramp is intended.

Example fix

// before
await ctx.runMutation(internal.broadcast.rampRunner.forceReleaseLease, { reason });

// after
const status = await ctx.runQuery(internal.broadcast.rampRunner.getRampStatus, {});
if (!status.configured) return { skipped: 'no ramp configured — no lease to release' };
await ctx.runMutation(internal.broadcast.rampRunner.forceReleaseLease, { reason });
Defensive patterns

Strategy: validation

Validate before calling

// Preflight before forceReleaseLease
const status = await ctx.runQuery(internal.broadcast.rampRunner.getRampStatus, {});
if (!status.configured) {
  return { skipped: 'no ramp configured — no lease to release' };
}

Type guard

function isConfigured(s: { configured: boolean }): s is { configured: true; pendingRunId?: string } {
  return s.configured === true;
}

Prevention

When it happens

Trigger: Calling forceReleaseLease before initRamp, after abortRamp deleted the row, or in an unseeded deployment.

Common situations: A lease appears stuck but the ramp was already aborted; tooling pointed at the wrong Convex project.

Related errors


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