paperclipai/paperclip · warning · QueuedCommentMutationError

queued_comment_stale_queue

queued_comment_stale_queue

Error message

The queued message targets a stale queue

What it means

requireMutationTarget guards queued-comment mutations (edit, reorder, discard) with optimistic concurrency checks. First it compares the caller's queueId against the current queue snapshot's queueId; if they differ, the message targets a queue generation that no longer exists, so it throws QueuedCommentMutationError code queued_comment_stale_queue. (A second check compares the revision for finer-grained conflicts.)

Solutions

  1. Refetch the current queue snapshot and retry the mutation with the new queueId and revision
  2. Handle code queued_comment_stale_queue in the UI by reloading the queue view and asking the user to re-apply the change
  3. Always source queueId/revision from the most recent GET of the queue, never cache across sessions
  4. Detect queue rotation events (wake completed, run changed) and invalidate cached queue handles

Example fix

// before
await discardQueuedComment(staleQueueId, rev, commentId);
// after
const snap = await getQueue(agentId);
await discardQueuedComment(snap.queueId, snap.revision, commentId);
Defensive patterns

Strategy: retry

Validate before calling

const snap = await getQueueSnapshot(agentId);
if (snap.queueId !== myQueueId) { await reloadQueue(); }
if (snap.revision !== myRevision) { await reloadQueue(); } // revision conflict is the sibling error

Try / catch

try { await discardQueuedComment(queueId, rev, id); }
catch (e) {
  if (e instanceof QueuedCommentMutationError && e.code === "queued_comment_stale_queue") {
    const snap = await getQueueSnapshot(agentId);
    await discardQueuedComment(snap.queueId, snap.revision, id); // one refresh+retry
  } else throw e;
}

Prevention

When it happens

Trigger: A client calls createEditQueuedComment / createReorderQueuedComments / createDiscardQueuedComment with a queueId that does not equal snapshot.queueId — the queue was recreated or rotated (e.g., after a wake, a config change, or a company/agent scope change) since the client fetched its snapshot.

Common situations: A long-lived UI tab holding an old queueId while the backend rebuilt the queue; an agent restart or heartbeat run change rotating the queue; concurrent sessions where one action invalidates the other's queue handle.

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/65511def5bd4e35c. Report an issue: GitHub.

Appendix: source

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

    readonly code: QueuedCommentMutationErrorCode,
    message: string,
  ) {
    super(message);
    this.name = "QueuedCommentMutationError";
  }
}

/** The route maps this onto the `forbidden(...)` HTTP error it threw before this move, using `message` unchanged. */
export class QueuedCommentMutationForbiddenError extends Error {
  constructor(message: string) {
    super(message);
    this.name = "QueuedCommentMutationForbiddenError";
  }
}

function requireMutationTarget(queue: QueuedCommentQueueSnapshot, queueId: string, revision: string): void {
  if (queue.queueId !== queueId) {
    throw new QueuedCommentMutationError("queued_comment_stale_queue", "The queued message targets a stale queue");
  }
  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) {

View on GitHub (pinned to 3f1d897a7c)