{"record":{"id":"c310bce2f934325f","repo":"toeverything/AFFiNE","slug":"comment-not-found","errorCode":"comment_not_found","errorMessage":"Comment not found.","messagePattern":"Comment not found\\.","errorType":"exception","errorClass":"CommentNotFound","httpStatus":404,"severity":"error","filePath":"packages/backend/server/src/core/comment/resolver.ts","lineNumber":111,"sourceCode":"      user: {\n        id: me.id,\n        name: me.name,\n        avatarUrl: me.avatarUrl,\n      },\n      replies: [],\n    };\n  }\n\n  @Mutation(() => Boolean, {\n    description: 'Update a comment content',\n  })\n  async updateComment(\n    @CurrentUser() me: UserType,\n    @Args('input') input: CommentUpdateInput\n  ) {\n    const comment = await this.service.getComment(input.id);\n    if (!comment) {\n      throw new CommentNotFound();\n    }\n\n    await this.assertPermission(me, comment, 'Doc.Comments.Update');\n\n    await this.service.updateComment(input);\n    publishCommentChanged(this.realtime, comment.workspaceId, comment.docId);\n    return true;\n  }\n\n  @Mutation(() => Boolean, {\n    description: 'Resolve a comment or not',\n  })\n  async resolveComment(\n    @CurrentUser() me: UserType,\n    @Args('input') input: CommentResolveInput\n  ) {\n    const comment = await this.service.getComment(input.id);\n    if (!comment) {","sourceCodeStart":93,"sourceCodeEnd":129,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/b4c8548c09da21b2898443559a5b846f0ccf5dd8/packages/backend/server/src/core/comment/resolver.ts#L93-L129","documentation":"GraphQL mutation updateComment (resolver.ts:111) loads the comment via service.getComment(input.id); when it returns null the resolver throws CommentNotFound before any permission check or update runs. It means the id does not match any comment row (unknown, deleted, or wrong workspace).","triggerScenarios":"Calling updateComment mutation with a comment id that does not exist or was already deleted - including the race where another user deletes the comment between render and submit.","commonSituations":"Stale comment panel state after a collaborator deleted the thread; double-submit after an error; id copied from another workspace/environment; comment cleaned up by a moderator.","solutions":["Refresh the comment list and retry the edit on an existing comment id.","If the comment was deleted, create a new comment instead of updating the old one.","Guard the UI: disable edit controls for comments missing from the latest listCommentChanges results."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"const comment = await getComment(input.id); // query comments for the doc first\nif (!comment) { /* refresh UI, do not send update */ }","typeGuard":"function isCommentNotFound(e: unknown): boolean {\n  return (\n    e instanceof GraphQLError &&\n    (e.extensions?.code === 'comment_not_found' ||\n      e.message === 'Comment not found.')\n  );\n}","tryCatchPattern":"try {\n  await updateComment({ id, content });\n} catch (e) {\n  if (isCommentNotFound(e)) {\n    // comment vanished: refetch thread, discard stale editor state, do not retry\n  } else throw e;\n}","preventionTips":["Bind edit actions to comments present in the latest commentChanges snapshot.","Close open editors when the commentChanged event removes that comment.","Never auto-retry an update against an id that returned comment_not_found."],"tags":["comments","graphql","entity-not-found","stale-reference","concurrency"],"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"}