koala73/worldmonitor · critical · Error
[runDailyRamp] rampCurve[${nextTier}] is undefined despite b
Error message
[runDailyRamp] rampCurve[${nextTier}] is undefined despite bounds check — config corruption? What it means
In runDailyRamp, after a bounds check confirms nextTier < rampCurve.length, the code indexes rampCurve[nextTier] and guards against undefined as a defensive measure (it quiets noUncheckedIndexedAccess and protects against a future code change breaking the bounds check). If this throw fires, the rampCurve array was mutated/shortened between the bounds check and the index, or the persisted config is internally inconsistent.
Source
Thrown at convex/broadcast/rampRunner.ts:1025
}
}
// ──── Step 2: figure out which tier to send next ────
const nextTier = row.currentTier + 1;
if (nextTier >= row.rampCurve.length) {
console.log("[runDailyRamp] ramp curve complete — deactivating");
await ctx.runMutation(
internal.broadcast.rampRunner._recordRunOutcome,
{ status: "ramp-complete", deactivate: true },
);
return { status: "ramp-complete" };
}
// Bounds-checked above; explicit guard quiets noUncheckedIndexedAccess
// and protects against a future code change that breaks the
// bounds check above without realising this index is now unsafe.
const count = row.rampCurve[nextTier];
if (count === undefined) {
throw new Error(
`[runDailyRamp] rampCurve[${nextTier}] is undefined despite bounds check — config corruption?`,
);
}
const waveLabel = `${row.waveLabelPrefix}-${nextTier + row.waveLabelOffset}`;
// ──── Step 3: in-flight guard via the new wave-loading state machine ────
// PR 2 (post-launch-stabilization plan, 2026-04-29) replaces the
// monolithic assignAndExportWave + createProLaunchBroadcast + sendProLaunchBroadcast
// chain with a self-driving multi-step pipeline that fits within the
// Convex 10-min action runtime budget at any wave size. The state lives
// on `waveRuns` + `wavePickedContacts`. See `convex/broadcast/waveRuns.ts`.
//
// The legacy `_claimTierForRun` + `_recordPendingExport` + `_recordPendingBroadcast`
// + `_recordWaveSent` + `_recordRunOutcome` mutations remain in this
// module so the operator-recovery commands (`recoverFromPartialFailure`,
// `clearPartialFailure`, `forceReleaseLease`) keep working on any
// legacy partial-failure rows. New runs go through the state machine.
//View on GitHub (pinned to ffec79ac33)
Solutions
- Inspect the broadcastRampConfig row's rampCurve and currentTier directly — confirm internal consistency.
- If corrupted, abortRamp and re-seed via initRamp with a valid rampCurve.
- Add a guard around any code path that mutates rampCurve post-init (it should be immutable after initRamp).
Defensive patterns
Strategy: validation
Validate before calling
// Validate config consistency before the runner indexes rampCurve
const row = await ctx.db.query('broadcastRampConfig').withIndex('by_key', (q) => q.eq('key', 'current')).first();
if (!row) throw new Error('no ramp configured');
const nextTier = row.currentTier + 1;
if (nextTier >= row.rampCurve.length || row.rampCurve[nextTier] === undefined) {
throw new Error(`rampCurve inconsistent at nextTier=${nextTier} (length=${row.rampCurve.length}) — config corruption; abort+reseed.`);
} Type guard
function rampCurveIsConsistent(row: { currentTier: number; rampCurve: number[] }): boolean {
return Array.isArray(row.rampCurve) && row.rampCurve.length > 0 && row.rampCurve.every((n) => typeof n === 'number' && Number.isInteger(n) && n > 0);
} Prevention
- Treat rampCurve as immutable after initRamp; never mutate it on the live config.
- If reconfiguration is needed, abortRamp then initRamp fresh — do not patch rampCurve in place.
- Add a table-level invariant test that broadcastRampConfig.rampCurve has no holes.
When it happens
Trigger: The broadcastRampConfig.rampCurve array was concurrently shortened (another mutation) between the length check and the index read; a corrupted/hand-edited config row has a rampCurve with holes or shorter than its recorded length; a Convex OCC retry saw a stale row.
Common situations: An operator mutation rewrote rampCurve mid-tick; a buggy script wrote an inconsistent row; extremely rare race on config rewrite.
Related errors
- [runDailyRamp] inFlight bounds-check invariant violated
- [pauseRamp] no ramp configured
- [resumeRamp] no ramp configured
- [resumeRamp] kill-gate is tripped; clearKillGate first after
- [clearKillGate] no ramp configured
AI-assisted analysis of koala73/worldmonitor@ffec79ac33 (2026-08-12).
Data as JSON: /api/errors/e9727c4f5a77917d.
Report an issue: GitHub.