koala73/worldmonitor · error · Error
[clearPartialFailure] refused: pending progress markers pres
Error message
[clearPartialFailure] refused: pending progress markers present (waveLabel=${row.pendingWaveLabel ?? "-"}, segmentId=${row.pendingSegmentId ?? "-"}, broadcastId=${row.pendingBroadcastId ?? "-"}). The export DID run; clearing here would mask stamped contacts. Use recoverFromPartialFailure instead. What it means
clearPartialFailure is a fail-closed guard: if any pending-progress marker (pendingWaveLabel, pendingSegmentId, pendingBroadcastId) is present, the export side of the wave actually executed and contacts may already be stamped. Clearing would mask a partially-sent wave. The handler throws and directs the operator to recoverFromPartialFailure instead.
Source
Thrown at convex/broadcast/rampRunner.ts:313
handler: async (ctx, { reason }) => {
const row = await loadConfig(ctx);
if (!row) throw new Error("[clearPartialFailure] no ramp configured");
if (row.lastRunStatus !== "partial-failure") {
return {
ok: true as const,
noop: true as const,
currentStatus: row.lastRunStatus,
};
}
// Fail-closed: if any pending-progress marker exists, the export DID make
// progress past `assignAndExportWave` — clearing here would mask a stamped
// / sent wave. Force the operator to use recoverFromPartialFailure.
if (
row.pendingWaveLabel ||
row.pendingSegmentId ||
row.pendingBroadcastId
) {
throw new Error(
`[clearPartialFailure] refused: pending progress markers present (waveLabel=${row.pendingWaveLabel ?? "-"}, segmentId=${row.pendingSegmentId ?? "-"}, broadcastId=${row.pendingBroadcastId ?? "-"}). The export DID run; clearing here would mask stamped contacts. Use recoverFromPartialFailure instead.`,
);
}
await ctx.db.patch(row._id, {
lastRunStatus: `partial-failure-cleared: ${reason.slice(0, 200)}`,
lastRunError: undefined,
pendingRunId: undefined,
pendingRunStartedAt: undefined,
});
return { ok: true as const };
},
});
/**
* Structured recovery for `lastRunStatus === "partial-failure"` that ALSO
* occurred AFTER `assignAndExportWave` succeeded (or after a forced lease
* release on a wedged run). Two recovery modes:
*View on GitHub (pinned to ffec79ac33)
Solutions
- Do not clear. Inspect the persisted pending* markers (getRampStatus / direct table read) to see how far the wave got.
- Use recoverFromPartialFailure({recovery:'manual-finished', ...}) if the broadcast actually sent, supplying broadcastId/segmentId/assigned/sentAt.
- Use recoverFromPartialFailure({recovery:'discard-and-rotate'}) only if you have verified the broadcast did NOT go out and you accept rotating the wave offset.
Example fix
// before
await ctx.runMutation(internal.broadcast.rampRunner.clearPartialFailure, { reason, confirmNoExport: true });
// after — pending markers present means the export ran; recover, do not clear
await ctx.runMutation(internal.broadcast.rampRunner.recoverFromPartialFailure, {
recovery: 'manual-finished',
reason,
broadcastId: row.pendingBroadcastId,
segmentId: row.pendingSegmentId,
assigned: row.pendingAssigned,
sentAt: confirmedSendTimestamp,
}); Defensive patterns
Strategy: validation
Validate before calling
// Before clearPartialFailure, detect pending-progress markers and route to recovery
const row = await ctx.db.query('broadcastRampConfig').withIndex('by_key', (q) => q.eq('key', 'current')).first();
if (row && (row.pendingWaveLabel || row.pendingSegmentId || row.pendingBroadcastId)) {
throw new Error('Pending progress markers present — use recoverFromPartialFailure, not clearPartialFailure.');
} Type guard
function hasPendingProgress(row: { pendingWaveLabel?: string; pendingSegmentId?: string; pendingBroadcastId?: string }): boolean {
return Boolean(row.pendingWaveLabel || row.pendingSegmentId || row.pendingBroadcastId);
} Prevention
- Treat clearPartialFailure as last-resort; prefer recoverFromPartialFailure whenever pending markers exist.
- Inspect pending* markers to determine how far the wave got before choosing manual-finished vs discard-and-rotate.
- Never clear pending state without confirming whether the Resend broadcast actually sent.
When it happens
Trigger: Calling clearPartialFailure when the failed run had already persisted pendingWaveLabel/pendingSegmentId/pendingBroadcastId via _recordPendingExport or _recordPendingBroadcast; i.e. the runner died after the export/broadcast side effect started but before _recordWaveSent committed.
Common situations: A wave action timed out or the Convex action was interrupted after assignAndExportWave or createProLaunchBroadcast ran; the operator tries the 'easy' clear instead of the proper recovery path.
Related errors
- [resumeRamp] kill-gate is tripped; clearKillGate first after
- [clearPartialFailure] no ramp configured
- [recoverFromPartialFailure] no ramp configured
- [recoverFromPartialFailure:manual-finished] currentTier=${ro
- [recoverFromPartialFailure:manual-finished] missing required
AI-assisted analysis of koala73/worldmonitor@ffec79ac33 (2026-08-12).
Data as JSON: /api/errors/4b7f673ba2494613.
Report an issue: GitHub.