{"record":{"id":"88c778163e995138","repo":"toeverything/AFFiNE","slug":"reply-not-found","errorCode":"reply_not_found","errorMessage":"Reply not found.","messagePattern":"Reply not found\\.","errorType":"exception","errorClass":"ReplyNotFound","httpStatus":404,"severity":"error","filePath":"packages/backend/server/src/core/comment/resolver.ts","lineNumber":202,"sourceCode":"      ...reply,\n      user: {\n        id: me.id,\n        name: me.name,\n        avatarUrl: me.avatarUrl,\n      },\n    };\n  }\n\n  @Mutation(() => Boolean, {\n    description: 'Update a reply content',\n  })\n  async updateReply(\n    @CurrentUser() me: UserType,\n    @Args('input') input: ReplyUpdateInput\n  ) {\n    const reply = await this.service.getReply(input.id);\n    if (!reply) {\n      throw new ReplyNotFound();\n    }\n\n    await this.assertPermission(me, reply, 'Doc.Comments.Update');\n\n    await this.service.updateReply(input);\n    publishCommentChanged(this.realtime, reply.workspaceId, reply.docId);\n    return true;\n  }\n\n  @Mutation(() => Boolean, {\n    description: 'Delete a reply',\n  })\n  async deleteReply(@CurrentUser() me: UserType, @Args('id') id: string) {\n    const reply = await this.service.getReply(id);\n    if (!reply) {\n      throw new ReplyNotFound();\n    }\n","sourceCodeStart":184,"sourceCodeEnd":220,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/26c515e050211269e911f7d9cfe162a26c83ed98/packages/backend/server/src/core/comment/resolver.ts#L184-L220","documentation":"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.","triggerScenarios":"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.","commonSituations":"Editing UI left open on a removed reply; optimistic local state not reconciled with deletes; content-edit collisions where one editor removes the reply.","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."],"exampleFix":"// before\nawait client.mutate({\n  mutation: UPDATE_REPLY,\n  variables: { input: { id: replyId, content } },\n});\n\n// after\ntry {\n  await client.mutate({\n    mutation: UPDATE_REPLY,\n    variables: { input: { id: replyId, content } },\n  });\n} catch (e) {\n  if (graphQLErrorCode(e) === 'reply_not_found') {\n    discardDraft(replyId);\n    toast('This reply is no longer available.');\n    return;\n  }\n  throw e;\n}","handlingStrategy":"try-catch","validationCode":"// Ensure the reply still exists in the latest comment list before editing\nconst { data } = await client.query({\n  query: COMMENTS_QUERY,\n  variables: { docId },\n  fetchPolicy: 'network-only',\n});\nconst replyStillThere = (data?.comments.edges ?? []).some(c =>\n  (c.node.replies ?? []).some(r => r.id === replyId),\n);\nif (!replyStillThere) {\n  discardDraft(replyId);\n  return;\n}","typeGuard":"function isReplyRef(value: unknown): value is { id: string } {\n  return typeof value === 'object' &&\n    value !== null &&\n    typeof (value as { id?: unknown }).id === 'string';\n}","tryCatchPattern":"try {\n  await mutateUpdateReply(replyId, content);\n} catch (e) {\n  const code = e?.graphQLErrors?.[0]?.extensions?.code;\n  if (code === 'reply_not_found') {\n    discardDraft(replyId);\n    toast('This reply is no longer available.');\n    return;\n  }\n  throw e;\n}","preventionTips":["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."],"tags":["graphql","comments","replies","not-found","concurrency"],"backgroundTag":null,"analyzedSha":"26c515e050211269e911f7d9cfe162a26c83ed98","analyzedAt":"2026-08-12T13:15:16.447Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}