koala73/worldmonitor · error · Error
[_recordWaveSent] tier moved underneath us: expected
Error message
[_recordWaveSent] tier moved underneath us: expected ${args.expectedCurrentTier}, found ${row.currentTier}. Refusing to overwrite. What it means
Optimistic-concurrency guard in _recordWaveSent: the config row's currentTier no longer matches the expectedCurrentTier the action captured when it started. Another writer advanced or reset the tier while this wave was in flight, and overwriting would skip or double-count a wave; the mutation refuses to proceed.
Solutions
- Re-read the config row to see the actual currentTier and who advanced it (logs, operator actions, a competing run)
- Reconcile the wave that was sent via recoverFromPartialFailure instead of forcing the tier write
Defensive patterns
Strategy: validation
When it happens
Trigger: Thrown at convex/broadcast/rampRunner.ts:784 when the library encounters an invalid state.
Common situations: See trigger scenarios.
AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-08-21).
Data as JSON: /api/errors/d68ae3a14ed02968.
Report an issue: GitHub.
Appendix: source
Thrown at convex/broadcast/rampRunner.ts:786
* record a successful wave-send. Validates that the lease still belongs to
* this runId AND clears all pending-progress markers.
*/
export const _recordWaveSent = internalMutation({
args: {
runId: v.string(),
expectedCurrentTier: v.number(),
newTier: v.number(),
waveLabel: v.string(),
broadcastId: v.string(),
segmentId: v.string(),
assigned: v.number(),
sentAt: v.number(),
},
handler: async (ctx, args) => {
const row = await loadConfig(ctx);
if (!row) throw new Error("[_recordWaveSent] no ramp configured");
if (row.currentTier !== args.expectedCurrentTier) {
throw new Error(
`[_recordWaveSent] tier moved underneath us: expected ${args.expectedCurrentTier}, found ${row.currentTier}. Refusing to overwrite.`,
);
}
if (row.pendingRunId !== args.runId) {
// The lease changed under us — operator force-released it, or
// recoverFromPartialFailure cleared it. We must NOT advance the tier;
// bubble to Convex auto-Sentry so ops can investigate.
throw new Error(
`[_recordWaveSent] lease lost: expected runId=${args.runId}, found ${row.pendingRunId ?? "<cleared>"}. Refusing to advance tier — investigate what cleared the lease.`,
);
}
await ctx.db.patch(row._id, {
currentTier: args.newTier,
lastWaveLabel: args.waveLabel,
lastWaveBroadcastId: args.broadcastId,
lastWaveSegmentId: args.segmentId,
lastWaveAssigned: args.assigned,
lastWaveSentAt: args.sentAt,View on GitHub (pinned to 7d06c8633d)