toeverything/AFFiNE · error · ReplyNotFound
reply_not_found
reply_not_found
Error message
Reply not found.
What it means
Thrown by the `updateReply` GraphQL mutation when `service.getReply(input.id)` returns null. The resolver fetches the reply to drive `assertPermission` and the realtime publish; a missing row aborts with `reply_not_found` (category `resource_not_found`) before any write.
Source
Thrown at packages/backend/server/src/core/comment/resolver.ts:202
...reply,
user: {
id: me.id,
name: me.name,
avatarUrl: me.avatarUrl,
},
};
}
@Mutation(() => Boolean, {
description: 'Update a reply content',
})
async updateReply(
@CurrentUser() me: UserType,
@Args('input') input: ReplyUpdateInput
) {
const reply = await this.service.getReply(input.id);
if (!reply) {
throw new ReplyNotFound();
}
await this.assertPermission(me, reply, 'Doc.Comments.Update');
await this.service.updateReply(input);
publishCommentChanged(this.realtime, reply.workspaceId, reply.docId);
return true;
}
@Mutation(() => Boolean, {
description: 'Delete a reply',
})
async deleteReply(@CurrentUser() me: UserType, @Args('id') id: string) {
const reply = await this.service.getReply(id);
if (!reply) {
throw new ReplyNotFound();
}
View on GitHub (pinned to 26c515e050)
Solutions
- Catch `reply_not_found` on edit and discard the local draft with a 'reply no longer available' notice.
- Reconcile the edit target against the latest `comments` query before opening the editor.
- Subscribe to comment-changes and close the editor when the reply's deletion arrives.
- Validate the reply id is non-empty and locally known before mutating.
Example fix
// before
await client.mutate({
mutation: UPDATE_REPLY,
variables: { input: { id: replyId, content } },
});
// after
try {
await client.mutate({
mutation: UPDATE_REPLY,
variables: { input: { id: replyId, content } },
});
} catch (e) {
if (graphQLErrorCode(e) === 'reply_not_found') {
discardDraft(replyId);
toast('This reply is no longer available.');
return;
}
throw e;
} Defensive patterns
Strategy: try-catch
Validate before calling
// Ensure the reply still exists in the latest comment list before editing
const { data } = await client.query({
query: COMMENTS_QUERY,
variables: { docId },
fetchPolicy: 'network-only',
});
const replyStillThere = (data?.comments.edges ?? []).some(c =>
(c.node.replies ?? []).some(r => r.id === replyId),
);
if (!replyStillThere) {
discardDraft(replyId);
return;
} Type guard
function isReplyRef(value: unknown): value is { id: string } {
return typeof value === 'object' &&
value !== null &&
typeof (value as { id?: unknown }).id === 'string';
} Try / catch
try {
await mutateUpdateReply(replyId, content);
} catch (e) {
const code = e?.graphQLErrors?.[0]?.extensions?.code;
if (code === 'reply_not_found') {
discardDraft(replyId);
toast('This reply is no longer available.');
return;
}
throw e;
} Prevention
- Close the editor when the comment-changes stream reports the reply's deletion.
- Re-validate the reply id against the fresh comment list before opening the editor.
- Keep edit sessions short or re-check on focus to avoid editing removed replies.
When it happens
Trigger: Editing a reply that was deleted by its author or a moderator; editing a reply whose id came from a stale list; race with another client deleting the reply mid-edit.
Common situations: Editing UI left open on a removed reply; optimistic local state not reconciled with deletes; content-edit collisions where one editor removes the reply.
Related errors
- comment_not_found
- comment_attachment_quota_exceeded
- comment_attachment_not_found
- blob_not_found
- doc_not_found
AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12).
Data as JSON: /api/errors/88c778163e995138.
Report an issue: GitHub.