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
- 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.
- If the status is a failure substatus, do not retry _markPickComplete; the run needs operator intervention (discardWaveRun or resumeStalledWaveRun).
- 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
- Ensure pickWaveAction schedules _markPickComplete exactly once per run.
- Handle the 'expected picking' error as idempotent rejection: if the run is already 'segment-created', continue; otherwise halt.
- Avoid retrying pickWaveAction wholesale after a partial failure without checking the run's current status.
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
- [_recordPoolFilterStats] no run ${args.runId}
- [_markPickComplete] no run ${args.runId}
- [_persistPickedBatch] chunk too large: ${contacts.length} >
- ALREADY_REVOKED
- [assignAndExportWave] waveLabel "${waveLabel}" already has s
AI-assisted analysis of koala73/worldmonitor@ffec79ac33 (2026-08-12).
Data as JSON: /api/errors/5327feddd25ee194.
Report an issue: GitHub.