koala73/worldmonitor · error · Error

[_markPickComplete] run ${args.runId} is ${run.status}, expe

Error message

[_markPickComplete] run ${args.runId} is ${run.status}, expected picking

What it means

Thrown by _markPickComplete when the waveRuns row exists but its status is not 'picking' — the expected state at the end of the pick phase. This is a state-machine invariant violation: the run has already transitioned (e.g. to 'segment-created', 'failed', or was discarded) and cannot be advanced again. The error reports the actual status found.

Source

Thrown at convex/broadcast/waveRuns.ts:604

/**
 * Transition a `picking`-status run to `segment-created` after pickWaveAction
 * has finished sampling, persisting, and creating the Resend segment.
 */
export const _markPickComplete = internalMutation({
  args: {
    runId: v.string(),
    segmentId: v.string(),
    totalCount: v.number(),
    underfilled: v.boolean(),
  },
  handler: async (ctx, args) => {
    const run = await ctx.db
      .query("waveRuns")
      .withIndex("by_runId", (q) => q.eq("runId", args.runId))
      .unique();
    if (!run) throw new Error(`[_markPickComplete] no run ${args.runId}`);
    if (run.status !== "picking") {
      throw new Error(
        `[_markPickComplete] run ${args.runId} is ${run.status}, expected picking`,
      );
    }
    const now = Date.now();
    await ctx.db.patch(run._id, {
      status: "segment-created",
      segmentId: args.segmentId,
      totalCount: args.totalCount,
      underfilled: args.underfilled,
      updatedAt: now,
    });
    return { ok: true };
  },
});

/**
 * Record a pick-phase failure. Lease policy depends on substatus:
 *   - 'empty-pool' clears the lease (terminal no-op; operator may retry next cycle)

View on GitHub (pinned to ffec79ac33)

Solutions

  1. Treat this as idempotent-rejection: if the run is already 'segment-created', the pick phase already completed and no action is needed — log and exit.
  2. If the status is a failure substatus, do not retry _markPickComplete; the run needs operator intervention (discardWaveRun or resumeStalledWaveRun).
  3. Prevent duplicate scheduling: ensure pickWaveAction does not schedule _markPickComplete more than once per run.
Defensive patterns

Strategy: try-catch

Try / catch

try {
  await ctx.runMutation(internal.broadcast.waveRuns._markPickComplete, { runId, segmentId, totalCount, underfilled });
} catch (err) {
  if (String(err).includes("expected picking")) {
    // run already advanced — check current status and treat as idempotent if already 'segment-created'
    const run = await ctx.runQuery(internal.broadcast.waveRuns._getRun, { runId });
    if (run?.status === "segment-created") return; // already done, safe to continue pipeline
  }
  throw err;
}

Prevention

When it happens

Trigger: _markPickComplete is called twice for the same runId (duplicate scheduled action, retry). The run was already marked failed by _markPickFailed. The operator ran discardWaveRun or resumeStalledWaveRun which transitioned the status. A replayed/delayed scheduled action fires after the run already advanced.

Common situations: A scheduled action fires twice (Convex at-least-once delivery). The operator manually advanced or discarded the run while pickWaveAction was still in flight. A retry of pickWaveAction after a partial failure re-attempts _markPickComplete.

Related errors


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