{"record":{"id":"a738ce2e5fbf99c2","repo":"koala73/worldmonitor","slug":"recordpendingbroadcast-no-ramp-configured","errorCode":null,"errorMessage":"[_recordPendingBroadcast] no ramp configured","messagePattern":"\\[_recordPendingBroadcast\\] no ramp configured","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"convex/broadcast/rampRunner.ts","lineNumber":750,"sourceCode":"});\n\n/**\n * Persist post-`createProLaunchBroadcast` progress. Called by the runner\n * AFTER `createProLaunchBroadcast` returns successfully. Lets\n * `recoverFromPartialFailure` recover the broadcastId without\n * operator-supplied metadata if the action dies between this point and a\n * successful `_recordWaveSent`.\n *\n * Lease-validating: same semantics as `_recordPendingExport`.\n */\nexport const _recordPendingBroadcast = internalMutation({\n  args: {\n    runId: v.string(),\n    broadcastId: v.string(),\n  },\n  handler: async (ctx, args) => {\n    const row = await loadConfig(ctx);\n    if (!row) throw new Error(\"[_recordPendingBroadcast] no ramp configured\");\n    if (row.pendingRunId !== args.runId) {\n      throw new Error(\n        `[_recordPendingBroadcast] lease lost: expected runId=${args.runId}, found ${row.pendingRunId ?? \"<cleared>\"}. Refusing to persist broadcast progress — operator/another run owns the state.`,\n      );\n    }\n    await ctx.db.patch(row._id, {\n      pendingBroadcastId: args.broadcastId,\n      pendingBroadcastAt: Date.now(),\n    });\n    return { ok: true as const };\n  },\n});\n\n/**\n * Internal mutation that the action calls to atomically advance the tier +\n * record a successful wave-send. Validates that the lease still belongs to\n * this runId AND clears all pending-progress markers.\n */","sourceCodeStart":732,"sourceCodeEnd":768,"githubUrl":"https://github.com/koala73/worldmonitor/blob/ffec79ac339946fd2d24e85845da5755dcaa534b/convex/broadcast/rampRunner.ts#L732-L768","documentation":"_recordPendingBroadcast is the internal mutation that persists pendingBroadcastId/pendingBroadcastAt after createProLaunchBroadcast returns a broadcast id. It loads the config via loadConfig and throws if no row exists, since recording broadcast progress against an aborted/non-existent ramp would corrupt recovery state.","triggerScenarios":"The runner reached the broadcast-record step but abortRamp deleted the config between run start and this mutation; or the run executes in an unseeded deployment.","commonSituations":"Operator aborted the ramp during an in-flight broadcast creation; deployment mismatch; parallel recovery cleared the config.","solutions":["Treat as non-retriable: the config is gone by intent. Let it surface to Sentry; decide whether to re-seed.","If accidental abort, re-seed via initRamp and re-run the wave (note a Resend broadcast may already have been created — dedupe manually).","Serialize operator abort against live runs."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Runner-side: re-check config existence before recording broadcast progress\nconst row = await ctx.db.query('broadcastRampConfig').withIndex('by_key', (q) => q.eq('key', 'current')).first();\nif (!row) {\n  return { aborted: 'config-gone' }; // operator aborted mid-flight; do not retry\n}","typeGuard":null,"tryCatchPattern":"try {\n  await ctx.runMutation(internal.broadcast.rampRunner._recordPendingBroadcast, { runId, broadcastId });\n} catch (e) {\n  console.error('[runner] _recordPendingBroadcast failed:', (e as Error).message);\n  throw e; // surface; reconcile manually — a broadcast may already exist in Resend\n}","preventionTips":["Avoid aborting the ramp during in-flight broadcast creation.","If this fires, check Resend for the created broadcast before re-running to avoid duplicates.","Serialize operator interventions against live runs."],"tags":["convex","broadcast-ramp","state-missing","lease","runner-internal"],"backgroundTag":null,"analyzedSha":"ffec79ac339946fd2d24e85845da5755dcaa534b","analyzedAt":"2026-08-12T11:24:56.012Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}