{"record":{"id":"a667b838ec7623e2","repo":"toeverything/AFFiNE","slug":"comment-not-found-a667b8","errorCode":"comment_not_found","errorMessage":"Comment not found.","messagePattern":"Comment not found\\.","errorType":"error_code","errorClass":"CommentNotFound","httpStatus":404,"severity":"error","filePath":"packages/backend/server/src/models/comment.ts","lineNumber":285,"sourceCode":"\n    return changes;\n  }\n\n  // #endregion\n\n  // #region Reply\n\n  /**\n   * Reply to a comment\n   * @param input - The reply create input\n   * @returns The created reply\n   */\n  async createReply(input: ReplyCreate) {\n    const data = ReplyCreateSchema.parse(input);\n    // find comment\n    const comment = await this.get(data.commentId);\n    if (!comment) {\n      throw new CommentNotFound();\n    }\n\n    return (await this.db.reply.create({\n      data: {\n        ...data,\n        workspaceId: comment.workspaceId,\n        docId: comment.docId,\n      },\n    })) as Reply;\n  }\n\n  async getReply(id: string) {\n    return (await this.db.reply.findUnique({\n      where: { id, deletedAt: null },\n    })) as Reply | null;\n  }\n\n  async listReplies(workspaceId: string, docId: string, commentId: string) {","sourceCodeStart":267,"sourceCodeEnd":303,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/26c515e050211269e911f7d9cfe162a26c83ed98/packages/backend/server/src/models/comment.ts#L267-L303","documentation":"CommentNotFound (resource_not_found / comment_not_found) thrown by CommentModel.createReply (packages/backend/server/src/models/comment.ts:285) when replying to a comment whose id does not resolve via this.get(data.commentId). The ReplyCreate schema parsed successfully, but the parent comment row is missing or not visible to the caller. Surfaced as a structured UserFriendlyError, so GraphQL/REST clients see a clean not_found.","triggerScenarios":"Calling createReply with a ReplyCreate payload whose commentId refers to a deleted, non-existent, or workspace-inaccessible comment. this.get() returns null (comment filtered by workspace/scope or simply absent), so the guard throws before inserting into the reply table.","commonSituations":"Client holds a stale commentId after the comment was deleted; reply posted to a comment in a different workspace; race where the comment is removed between UI load and reply submit; test fixture that forgot to seed the parent comment.","solutions":["Verify the comment exists and is in scope before submitting the reply: GET /workspaces/{ws}/docs/{doc}/comments/{commentId}.","If the comment was deleted, discard the reply draft or recreate the parent comment first.","Catch CommentNotFound by code and surface a user-facing 'This comment no longer exists' message in the UI.","In tests, ensure the parent comment is created in the same workspace before createReply."],"exampleFix":"// before\nawait commentModel.createReply({ commentId, body });\n// after\nconst parent = await commentModel.get(commentId);\nif (!parent) throw new Error('Cannot reply: parent comment is gone.');\nawait commentModel.createReply({ commentId, body });","handlingStrategy":"validation","validationCode":"const parent = await commentModel.get(commentId);\nif (!parent) {\n  throw new Error(`Cannot reply: parent comment ${commentId} not found`);\n}\nawait commentModel.createReply({ commentId, body });","typeGuard":"const commentExists = (c: Comment | null): c is Comment => c !== null;","tryCatchPattern":"try {\n  await commentModel.createReply(input);\n} catch (e) {\n  if (e instanceof UserFriendlyError && e.code === 'comment_not_found') {\n    res.status(404).send('This comment no longer exists.');\n    return;\n  }\n  throw e;\n}","preventionTips":["Verify the parent comment is visible to the caller before opening the reply composer.","Drop reply drafts when the parent comment is reported deleted by the server.","In tests, always seed the parent comment in the same workspace before createReply."],"tags":["comment","reply","graphql","not-found"],"backgroundTag":null,"analyzedSha":"26c515e050211269e911f7d9cfe162a26c83ed98","analyzedAt":"2026-08-12T13:15:16.447Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}