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
- Preflight with getRampStatus; if {configured:false} there is no lease to release.
- If abortRamp ran, the lease was deleted with the row — no action needed.
- 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
- Preflight forceReleaseLease with getRampStatus.
- After abortRamp the lease is gone with the row — no release needed.
- Reserve forceReleaseLease for genuinely stuck leases; verify no live run holds it first.
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
- [_recordPendingExport] no ramp configured
- [_recordPendingBroadcast] no ramp configured
- [_recordWaveSent] no ramp configured
- [pauseRamp] no ramp configured
- [resumeRamp] no ramp configured
AI-assisted analysis of koala73/worldmonitor@ffec79ac33 (2026-08-12).
Data as JSON: /api/errors/c2ff16c7dc2ffbcd.
Report an issue: GitHub.