{"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/b4c8548c09da21b2898443559a5b846f0ccf5dd8/packages/backend/server/src/core/comment/resolver.ts#L184-L220","documentation":"GraphQL mutation updateReply (resolver.ts:202) throws ReplyNotFound when service.getReply(input.id) returns null. The reply (comment sub-item) does not exist; the check runs before 'Doc.Comments.Update' permission and before service.updateReply.","triggerScenarios":"Calling updateReply with a reply id that is deleted or unknown - e.g. the parent comment (and its replies) was deleted while the reply editor was open.","commonSituations":"Editing a reply in a thread that a collaborator deleted (deleting a comment removes its replies); stale reply id from an outdated subscription snapshot.","solutions":["Refetch the thread and only edit replies still present.","Treat a vanished parent comment as removing all its replies; drop local reply state with it.","Keep the reply editor bound to a live subscription so deleted replies disable editing immediately."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"const replyAlive = (await comments(docId)).some(c => c.replies?.some(r => r.id === input.id));\nif (!replyAlive) { /* disable edit action */ }","typeGuard":"function isReplyNotFound(e: unknown): boolean {\n  return (\n    e instanceof GraphQLError &&\n    (e.extensions?.code === 'reply_not_found' ||\n      e.message === 'Reply not found.')\n  );\n}","tryCatchPattern":"try {\n  await updateReply({ id, content });\n} catch (e) {\n  if (isReplyNotFound(e)) {\n    // reply (or its parent) is gone: refetch thread, drop stale editor\n  } else throw e;\n}","preventionTips":["Deleting a parent comment removes its replies - invalidate reply state with the parent.","Bind reply editors to the live subscription snapshot for that doc.","Do not retry an update against a reply id that already 404'd."],"tags":["comments","replies","graphql","entity-not-found","stale-reference"],"backgroundTag":"entity-not-found","analyzedSha":"b4c8548c09da21b2898443559a5b846f0ccf5dd8","analyzedAt":"2026-08-18T21:16:52.546Z","contentChangedAt":"2026-08-18T21:16:52.546Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}