{"record":{"id":"65511def5bd4e35c","repo":"paperclipai/paperclip","slug":"queued-comment-stale-queue","errorCode":"queued_comment_stale_queue","errorMessage":"The queued message targets a stale queue","messagePattern":"The queued message targets a stale queue","errorType":"error_code","errorClass":"QueuedCommentMutationError","httpStatus":null,"severity":"warning","filePath":"server/src/modules/wake-queue/application/queued-comment-use-cases.ts","lineNumber":44,"sourceCode":"    readonly code: QueuedCommentMutationErrorCode,\n    message: string,\n  ) {\n    super(message);\n    this.name = \"QueuedCommentMutationError\";\n  }\n}\n\n/** The route maps this onto the `forbidden(...)` HTTP error it threw before this move, using `message` unchanged. */\nexport class QueuedCommentMutationForbiddenError extends Error {\n  constructor(message: string) {\n    super(message);\n    this.name = \"QueuedCommentMutationForbiddenError\";\n  }\n}\n\nfunction requireMutationTarget(queue: QueuedCommentQueueSnapshot, queueId: string, revision: string): void {\n  if (queue.queueId !== queueId) {\n    throw new QueuedCommentMutationError(\"queued_comment_stale_queue\", \"The queued message targets a stale queue\");\n  }\n  if (queue.revision !== revision) {\n    throw new QueuedCommentMutationError(\"queued_comment_revision_conflict\", \"The queued messages changed in another session\");\n  }\n}\n\nasync function updateQueueRunCommentIdsGuarded(\n  tx: QueuedCommentQueueTransaction,\n  input: { queueRun: QueuedCommentRunRow | null; ids: string[]; updatedAt: Date },\n): Promise<QueuedCommentRunRow | null> {\n  if (!input.queueRun) return null;\n  const updated = await tx.updateQueueRunCommentIds({\n    queueRunId: input.queueRun.id,\n    contextSnapshot: input.queueRun.contextSnapshot,\n    ids: input.ids,\n    updatedAt: input.updatedAt,\n  });\n  if (!updated) {","sourceCodeStart":26,"sourceCodeEnd":62,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/server/src/modules/wake-queue/application/queued-comment-use-cases.ts#L26-L62","documentation":"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.)","triggerScenarios":"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.","commonSituations":"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.","solutions":["Refetch the current queue snapshot and retry the mutation with the new queueId and revision","Handle code queued_comment_stale_queue in the UI by reloading the queue view and asking the user to re-apply the change","Always source queueId/revision from the most recent GET of the queue, never cache across sessions","Detect queue rotation events (wake completed, run changed) and invalidate cached queue handles"],"exampleFix":"// before\nawait discardQueuedComment(staleQueueId, rev, commentId);\n// after\nconst snap = await getQueue(agentId);\nawait discardQueuedComment(snap.queueId, snap.revision, commentId);","handlingStrategy":"retry","validationCode":"const snap = await getQueueSnapshot(agentId);\nif (snap.queueId !== myQueueId) { await reloadQueue(); }\nif (snap.revision !== myRevision) { await reloadQueue(); } // revision conflict is the sibling error","typeGuard":null,"tryCatchPattern":"try { await discardQueuedComment(queueId, rev, id); }\ncatch (e) {\n  if (e instanceof QueuedCommentMutationError && e.code === \"queued_comment_stale_queue\") {\n    const snap = await getQueueSnapshot(agentId);\n    await discardQueuedComment(snap.queueId, snap.revision, id); // one refresh+retry\n  } else throw e;\n}","preventionTips":["Always pass queueId/revision from the most recent snapshot, never cached values","Reload the queue view whenever a wake/run rotation may have occurred","Handle queued_comment_stale_queue by refreshing, not blind-retrying","Version-check the queue in the UI before rendering mutation controls"],"tags":["concurrency","queue","optimistic-locking","stale-state"],"backgroundTag":"invalid-state-transition","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-22T10:30:35.592Z"}