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

  1. Re-read the queue state; if the message is now dispatched, treat the mutation as no longer applicable and refresh the UI
  2. Retry the mutation after a short delay only if the queue returns to a pending state
  3. Do not retry blindly during an active dispatch window — surface 'message is being sent' to the user instead
  4. 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

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


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)