paperclipai/paperclip · error · QueuedCommentMutationError
queued_comment_already_dispatching
queued_comment_already_dispatching
Error message
The queued message is already being dispatched
What it means
QueuedCommentMutationError with code queued_comment_already_dispatching. Thrown by updateQueueRunCommentIdsGuarded when the guarded conditional update of the queue run's comment-ids row affects no rows (updated is falsy). The conditional update only matches while the run is still mutable; if the queueRun row was already taken by the dispatcher, the update matches nothing and the library concludes the queued message is being dispatched concurrently.
Solutions
- Re-read the queue state; if the message is now dispatched, treat the mutation as no longer applicable and refresh the UI
- Retry the mutation after a short delay only if the queue returns to a pending state
- Do not retry blindly during an active dispatch window — surface 'message is being sent' to the user instead
- Guard client-side by disabling edit/discard controls once a dispatch has been triggered
Example fix
// before
await discardQueuedComment({ issue, actor, queueId, revision, commentId }); // races with dispatch
// after
try {
await discardQueuedComment({ issue, actor, queueId, revision, commentId });
} catch (e) {
if (e.code === 'queued_comment_already_dispatching') {
// message is being sent: refresh state, do not retry now
}
} Defensive patterns
Strategy: try-catch
Type guard
function isDispatchSafe(run: { status: string } | null): boolean {
return run !== null && run.status === 'pending';
} Try / catch
try {
await discardQueuedComment(input);
} catch (e) {
if (e?.code === 'queued_comment_already_dispatching') {
await refreshQueue(); // dispatch in progress; surface status, no retry now
return;
}
throw e;
} Prevention
- Disable edit/discard controls once a dispatch is triggered
- Re-read queue state before mutations after any dispatch activity
- Avoid mutating during scheduled wake/flush windows
- Retry only after confirming the queue returned to pending
When it happens
Trigger: Calling updatedQueueRun or createDiscardQueuedComment paths that touch the queue run when a dispatcher (wake/flush worker) has concurrently claimed or is draining the same queue run row between the read of queueRun and the guarded update.
Common situations: A user discards or edits a queued message at the exact moment the scheduled wake fires; a slow UI holding a stale queueRun snapshot while the dispatcher advances; concurrent workers racing on the same queue run row.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Bridge envelope changed while reading.
- Capability live turn admission was abandoned during teardown
- Capability live turn start completed after admission…
- codex_run_attach_busy
- CreateOS lease cleanup is already in progress.
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/3d45cf54fc4a1a17.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/modules/wake-queue/application/queued-comment-use-cases.ts:63
}
if (queue.revision !== revision) {
throw new QueuedCommentMutationError("queued_comment_revision_conflict", "The queued messages changed in another session");
}
}
async function updateQueueRunCommentIdsGuarded(
tx: QueuedCommentQueueTransaction,
input: { queueRun: QueuedCommentRunRow | null; ids: string[]; updatedAt: Date },
): Promise<QueuedCommentRunRow | null> {
if (!input.queueRun) return null;
const updated = await tx.updateQueueRunCommentIds({
queueRunId: input.queueRun.id,
contextSnapshot: input.queueRun.contextSnapshot,
ids: input.ids,
updatedAt: input.updatedAt,
});
if (!updated) {
throw new QueuedCommentMutationError("queued_comment_already_dispatching", "The queued message is already being dispatched");
}
return updated;
}
export type EditQueuedCommentInput = {
issue: QueuedCommentIssueContext;
actor: QueuedCommentActor;
commentId: string;
queueId: string;
revision: string;
body: string;
now: Date;
};
export type EditQueuedCommentResult = {
queue: QueuedCommentQueueSnapshot;
activityPublication: QueuedCommentActivityPublication;
};View on GitHub (pinned to 3f1d897a7c)