paperclipai/paperclip · error · QueuedCommentMutationForbiddenError
Only the queued message author can edit it
Error message
Only the queued message author can edit it
What it means
QueuedCommentMutationForbiddenError with message 'Only the queued message author can edit it', thrown from createEditQueuedComment when the targeted pending entry exists but its canEdit flag is false. canEdit is computed per actor: only the author of the queued comment may edit its body, so edits by any other actor are rejected with 403-style semantics rather than a conflict code.
Solutions
- Have the original author perform the edit, or have them discard it and queue a corrected message yourself
- Discard your own new queued message instead of editing the other actor's entry
- If the product should allow broader editing, change the canEdit computation in the queue snapshot logic — this is a policy change, not a client fix
- Update the UI to render the edit control only when entry.canEdit is true
Example fix
// before
await editQueuedComment({ issue, actor: otherActor, queueId, revision, commentId, body });
// after
const queue = await getQueuedComments({ issue, actor });
const entry = queue.entries.find((e) => e.comment.id === commentId);
if (!entry?.canEdit) {
// show 'only the author can edit' and offer discard/requeue instead
}
await editQueuedComment({ issue, actor, queueId, revision: queue.revision, commentId, body }); Defensive patterns
Strategy: type-guard
Validate before calling
const queue = await getQueuedComments({ issue, actor });
const entry = queue.entries.find((e) => e.comment.id === commentId);
if (!entry?.canEdit) throw new Error('only the author can edit this queued message'); Type guard
function canEditEntry(entry: { comment: { id: string }; canEdit: boolean } | undefined): entry is { comment: { id: string }; canEdit: true } {
return entry !== undefined && entry.canEdit;
} Try / catch
try {
await editQueuedComment(input);
} catch (e) {
if (e instanceof QueuedCommentMutationForbiddenError || /author can edit/.test(e?.message ?? '')) {
showOnlyAuthorCanEditNotice();
return;
}
throw e;
} Prevention
- Render edit controls only when entry.canEdit is true
- Discard-and-requeue instead of editing another actor's message
- Check the author of the queued comment before opening the editor
- Treat this as policy, not a bug: only the author may edit
When it happens
Trigger: Calling editQueuedComment as an actor who is not the author of the pending queued comment — e.g. an admin or teammate trying to edit someone else's queued message, or an agent API key editing a comment queued by the board user.
Common situations: Operator attempting to fix a typo in a colleague's queued message; shared inbox where multiple actors view the same issue queue; UI incorrectly enabling the edit button for non-author viewers.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
Related errors
- not_authorized
- Only the queued message author can discard it
- paperclip_runner_chat_attachment_read_not_authorized
- railway_api_authorization_required
- access.reasonCode
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/3249c206fe82374c.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/modules/wake-queue/application/queued-comment-use-cases.ts:94
};
export type EditQueuedCommentResult = {
queue: QueuedCommentQueueSnapshot;
activityPublication: QueuedCommentActivityPublication;
};
export function createEditQueuedComment(deps: { issueLock: QueuedCommentIssueLockWriter }) {
return async function editQueuedComment(input: EditQueuedCommentInput): Promise<EditQueuedCommentResult> {
return deps.issueLock.withLockedQueue(
{ issue: input.issue, actor: input.actor, queueId: input.queueId },
async (locked, tx) => {
requireMutationTarget(locked.queue, input.queueId, input.revision);
const entry = locked.queue.entries.find((candidate) => candidate.comment.id === input.commentId);
if (!entry) {
throw new QueuedCommentMutationError("queued_comment_not_pending", "The queued message is no longer pending");
}
if (!entry.canEdit) {
throw new QueuedCommentMutationForbiddenError("Only the queued message author can edit it");
}
const updated = await tx.updateCommentBody({
issueId: input.issue.id,
commentId: input.commentId,
body: input.body,
updatedAt: input.now,
});
if (!updated) {
throw new QueuedCommentMutationError("queued_comment_not_pending", "The queued message is no longer pending");
}
await tx.touchIssueUpdatedAt({ issueId: input.issue.id, updatedAt: input.now });
await tx.syncCommentReferences(input.commentId);
await tx.syncCommentExternalObjectsSafely(input.commentId);
const ids = locked.queue.entries.map((candidate) => candidate.comment.id);
const updatedQueueRun = await updateQueueRunCommentIdsGuarded(tx, {
queueRun: locked.queueRun,View on GitHub (pinned to 3f1d897a7c)