koala73/worldmonitor · error · Error

[resumeRamp] kill-gate is tripped; clearKillGate first after

Error message

[resumeRamp] kill-gate is tripped; clearKillGate first after investigating.

What it means

resumeRamp refuses to flip active=true when broadcastRampConfig.killGateTripped is true. The kill-gate is tripped by the runner when a prior wave's bounce or complaint rate exceeded the configured threshold (DEFAULT_BOUNCE_KILL_THRESHOLD / DEFAULT_COMPLAINT_KILL_THRESHOLD). Resuming would re-enable sending while sender reputation is at risk, so the operator must explicitly clear the gate after investigating.

Source

Thrown at convex/broadcast/rampRunner.ts:235

});

export const pauseRamp = internalMutation({
  args: {},
  handler: async (ctx) => {
    const row = await loadConfig(ctx);
    if (!row) throw new Error("[pauseRamp] no ramp configured");
    await ctx.db.patch(row._id, { active: false });
    return { ok: true, prevActive: row.active };
  },
});

export const resumeRamp = internalMutation({
  args: {},
  handler: async (ctx) => {
    const row = await loadConfig(ctx);
    if (!row) throw new Error("[resumeRamp] no ramp configured");
    if (row.killGateTripped) {
      throw new Error(
        "[resumeRamp] kill-gate is tripped; clearKillGate first after investigating.",
      );
    }
    await ctx.db.patch(row._id, { active: true });
    return { ok: true };
  },
});

export const clearKillGate = internalMutation({
  args: { reason: v.string() },
  handler: async (ctx, { reason }) => {
    const row = await loadConfig(ctx);
    if (!row) throw new Error("[clearKillGate] no ramp configured");
    if (!row.killGateTripped) {
      return { ok: true, noop: true };
    }
    await ctx.db.patch(row._id, {
      killGateTripped: false,

View on GitHub (pinned to ffec79ac33)

Solutions

  1. Investigate the trip: check getRampStatus().killGateReason and the last wave's Resend bounce/complaint stats.
  2. Resolve the list/content issue (suppress hard bounces, fix content causing complaints), then call clearKillGate({reason}) with a documented justification.
  3. Then call resumeRamp to re-enable the cron.

Example fix

// before
await ctx.runMutation(internal.broadcast.rampRunner.resumeRamp, {});

// after
const status = await ctx.runQuery(internal.broadcast.rampRunner.getRampStatus, {});
if (status.configured && status.killGateTripped) {
  throw new Error(`Kill-gate tripped: ${status.killGateReason}. Investigate, then clearKillGate.`);
}
await ctx.runMutation(internal.broadcast.rampRunner.resumeRamp, {});
Defensive patterns

Strategy: validation

Validate before calling

// Preflight before resumeRamp — block if kill-gate is tripped
const status = await ctx.runQuery(internal.broadcast.rampRunner.getRampStatus, {});
if (status.configured && status.killGateTripped) {
  throw new Error(`Kill-gate tripped (${status.killGateReason}). Investigate, then clearKillGate before resumeRamp.`);
}

Type guard

function killGateIsTripped(s: { configured: boolean; killGateTripped?: boolean }): boolean {
  return s.configured === true && s.killGateTripped === true;
}

Prevention

When it happens

Trigger: Calling resumeRamp after the daily runner recorded a kill-gate trip; calling it after manually setting killGateTripped=true; resuming while the last wave's Resend stats still show out-of-threshold bounce/complaint.

Common situations: A wave hit a hard-bounce spike (stale list) or complaint spike and the runner auto-tripped the gate; the operator wants to resume but has not yet diagnosed root cause.

Related errors


AI-assisted analysis of koala73/worldmonitor@ffec79ac33 (2026-08-12). Data as JSON: /api/errors/8811b067a4510a9b. Report an issue: GitHub.