paperclipai/paperclip · warning · QueuedCommentMutationError

queued_comment_order_mismatch

queued_comment_order_mismatch

Error message

The queued message order does not match the current queue

What it means

This error is thrown by createReorderQueuedComments when the caller-supplied orderedCommentIds do not correspond exactly to the set of comment IDs currently in the wake queue. The domain policy decideQueuedCommentReorder returns a 'mismatch' decision, meaning the reorder payload references a stale view of the queue. It maps 1:1 onto the HTTP 409 conflict the route previously returned, with code queued_comment_order_mismatch.

Solutions

  1. Refetch the current queue snapshot and re-derive orderedCommentIds from it, then retry the reorder with a fresh revision.
  2. Handle the 409/conflict code queued_comment_order_mismatch in the client by reloading the queue UI instead of retrying blindly.
  3. Ensure the client always submits the exact ID set from the latest queue.revision it holds; never merge ID lists from multiple snapshots.
  4. Remove filtering logic on the client that drops entries (e.g. hidden/collapsed messages) before sending the ID list — the payload must be a complete permutation of current IDs.

Example fix

// before
await api.reorderQueuedComments({ issueId, queueId, revision: staleRevision, orderedCommentIds: cachedIds });
// after
const queue = await api.getQueuedComments({ issueId });
await api.reorderQueuedComments({ issueId, queueId, revision: queue.revision, orderedCommentIds: queue.entries.map(e => e.comment.id) });
Defensive patterns

Strategy: validation

Validate before calling

function canReorder(queue, orderedIds) {
  const current = queue.entries.map(e => e.comment.id).sort();
  const submitted = [...orderedIds].sort();
  return queue.revision != null && current.length === submitted.length && current.every((id, i) => id === submitted[i]);
}

Type guard

function isFreshQueueSnapshot(queue, revision) {
  return typeof queue?.revision === "string" && queue.revision === revision;
}

Try / catch

try {
  await reorderQueuedComments(input);
} catch (e) {
  if (e instanceof QueuedCommentMutationError && e.code === "queued_comment_order_mismatch") {
    await refreshQueue(); // reload snapshot + revision, let user re-apply
  } else throw e;
}

Prevention

When it happens

Trigger: Calling reorderQueuedComments (or the queue-discard/create use cases built from createReorderQueuedComments) with orderedCommentIds that: omit an existing queued comment, include a comment ID no longer in the queue (already dispatched, discarded, or deleted), or duplicate/misspell an ID. The revision check (requireMutationTarget) passed first, so the revision matched but the ID list still diverged.

Common situations: Two browser tabs both open on the issue queue: tab A reorders, tab B then submits its reorder with the pre-change ID list. A queued comment was dispatched between the client's snapshot fetch and the reorder submit. A client cached an old queue snapshot and reorders without refetching. Agent automation constructing orderedCommentIds from stale API responses.

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/806c87afbff5ca5e. Report an issue: GitHub.

Appendix: source

Thrown at server/src/modules/wake-queue/application/queued-comment-use-cases.ts:171

  now: Date;
};

export type ReorderQueuedCommentsResult = {
  queue: QueuedCommentQueueSnapshot;
  activityPublication: QueuedCommentActivityPublication;
};

export function createReorderQueuedComments(deps: { issueLock: QueuedCommentIssueLockWriter }) {
  return async function reorderQueuedComments(input: ReorderQueuedCommentsInput): Promise<ReorderQueuedCommentsResult> {
    return deps.issueLock.withLockedQueue(
      { issue: input.issue, actor: input.actor, queueId: input.queueId },
      async (locked, tx) => {
        requireMutationTarget(locked.queue, input.queueId, input.revision);

        const currentIds = locked.queue.entries.map((entry) => entry.comment.id);
        const reorderDecision = decideQueuedCommentReorder({ currentIds, orderedIds: input.orderedCommentIds });
        if (reorderDecision.kind === "mismatch") {
          throw new QueuedCommentMutationError(
            "queued_comment_order_mismatch",
            "The queued message order does not match the current queue",
          );
        }

        const updatedWake = await tx.updateWakeQueuedCommentIds({
          wakeId: locked.wake.id,
          payload: locked.wake.payload,
          ids: input.orderedCommentIds,
          updatedAt: input.now,
        });
        const updatedQueueRun = await updateQueueRunCommentIdsGuarded(tx, {
          queueRun: locked.queueRun,
          ids: input.orderedCommentIds,
          updatedAt: input.now,
        });

        const queue = await tx.buildQueueSnapshot({

View on GitHub (pinned to 3f1d897a7c)