koala73/worldmonitor · error · Error
[_recordPendingExport] no ramp configured
Error message
[_recordPendingExport] no ramp configured
What it means
_recordPendingExport is an internal mutation called by the runner to persist export-side progress (pendingWaveLabel/pendingSegmentId/pendingAssigned) after assignAndExportWave succeeds but before the broadcast side effect. It loads the config via loadConfig and throws if no row exists — the runner cannot persist progress against a config that was aborted mid-flight.
Source
Thrown at convex/broadcast/rampRunner.ts:718
* `assignAndExportWave` returns successfully. Lets `recoverFromPartialFailure`
* recover the (segmentId, assigned, waveLabel) without operator-supplied
* metadata if the action dies between this point and a successful
* `_recordWaveSent`.
*
* Lease-validating: throws if the lease has changed (operator
* `forceReleaseLease` mid-flight, or a different run claimed). The throw
* bubbles to Convex auto-Sentry; the runner stops without advancing.
*/
export const _recordPendingExport = internalMutation({
args: {
runId: v.string(),
waveLabel: v.string(),
segmentId: v.string(),
assigned: v.number(),
},
handler: async (ctx, args) => {
const row = await loadConfig(ctx);
if (!row) throw new Error("[_recordPendingExport] no ramp configured");
if (row.pendingRunId !== args.runId) {
throw new Error(
`[_recordPendingExport] lease lost: expected runId=${args.runId}, found ${row.pendingRunId ?? "<cleared>"}. Refusing to persist export progress — operator/another run owns the state.`,
);
}
await ctx.db.patch(row._id, {
pendingWaveLabel: args.waveLabel,
pendingSegmentId: args.segmentId,
pendingAssigned: args.assigned,
pendingExportAt: Date.now(),
});
return { ok: true as const };
},
});
/**
* Persist post-`createProLaunchBroadcast` progress. Called by the runner
* AFTER `createProLaunchBroadcast` returns successfully. LetsView on GitHub (pinned to ffec79ac33)
Solutions
- Do not retry — the config is gone by operator intent. Let the run fail and surface to Sentry, then decide whether to re-seed.
- If the abort was accidental, re-seed via initRamp and re-trigger the wave fresh (the export did not get recorded).
- Ensure only one operator path (abortRamp vs. live runner) mutates the config at a time.
Defensive patterns
Strategy: try-catch
Validate before calling
// Runner-side: re-check config existence right before persisting export progress
const row = await ctx.db.query('broadcastRampConfig').withIndex('by_key', (q) => q.eq('key', 'current')).first();
if (!row) {
// abort this run cleanly; operator aborted the ramp mid-flight
return { aborted: 'config-gone' };
} Try / catch
try {
await ctx.runMutation(internal.broadcast.rampRunner._recordPendingExport, { runId, waveLabel, segmentId, assigned });
} catch (e) {
// config gone or lease lost — do NOT retry; surface and let ops reconcile
console.error('[runner] _recordPendingExport failed:', (e as Error).message);
throw e;
} Prevention
- Serialize operator aborts against live runs to avoid deleting the config mid-flight.
- Runner should treat this as terminal, not retriable.
- Confirm the deployment is seeded before triggering waves.
When it happens
Trigger: The runner reached the export-record step but abortRamp deleted the config row between the run starting and this mutation; or the run is executing in an unseeded deployment.
Common situations: Operator aborted the ramp while a wave action was in flight; a parallel run/recovery cleared the config; deployment mismatch.
Related errors
- [_recordPendingBroadcast] no ramp configured
- [_recordWaveSent] no ramp configured
- [forceReleaseLease] no ramp configured
- [_recordPendingExport] lease lost: expected runId=${args.run
- [_recordPendingBroadcast] lease lost: expected runId=${args.
AI-assisted analysis of koala73/worldmonitor@ffec79ac33 (2026-08-12).
Data as JSON: /api/errors/8b7534bcd312af29.
Report an issue: GitHub.