{"record":{"id":"3a0a8be37de56e3b","repo":"toeverything/AFFiNE","slug":"mention-user-doc-access-denied","errorCode":"mention_user_doc_access_denied","errorMessage":"Mentioned user can not access doc ${docId}.","messagePattern":"Mentioned user can not access doc (.+?)\\.","errorType":"exception","errorClass":"MentionUserDocAccessDenied","httpStatus":403,"severity":"warning","filePath":"packages/backend/server/src/core/notification/resolver.ts","lineNumber":71,"sourceCode":"        createdByUserId: me.id,\n      },\n    });\n    if (parsedInput.userId === me.id) {\n      throw new MentionUserOneselfDenied();\n    }\n    // currentUser can update the doc\n    await this.ac\n      .user(me.id)\n      .doc(parsedInput.body.workspaceId, parsedInput.body.doc.id)\n      .assert('Doc.Update');\n    // mention user can read the doc\n    if (\n      !(await this.ac\n        .user(parsedInput.userId)\n        .doc(parsedInput.body.workspaceId, parsedInput.body.doc.id)\n        .can('Doc.Read'))\n    ) {\n      throw new MentionUserDocAccessDenied({\n        docId: parsedInput.body.doc.id,\n      });\n    }\n    const notification = await this.service.createMention(parsedInput);\n    return notification.id;\n  }\n\n  @Mutation(() => Boolean, {\n    description: 'mark notification as read',\n  })\n  async readNotification(\n    @CurrentUser() me: UserType,\n    @Args('id') notificationId: string\n  ) {\n    await this.service.markAsRead(me.id, notificationId);\n    return true;\n  }\n","sourceCodeStart":53,"sourceCodeEnd":89,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/26c515e050211269e911f7d9cfe162a26c83ed98/packages/backend/server/src/core/notification/resolver.ts#L53-L89","documentation":"MentionUserDocAccessDenied (code=mention_user_doc_access_denied) thrown by the mention mutation when the mentioned user lacks Doc.Read on the target doc. Mentions implicitly grant the recipient the ability to see the doc reference; if they can't read the doc, the mention is rejected. The check runs after the current user's Doc.Update is verified, so the author is allowed but the recipient is not.","triggerScenarios":"Mentioning an external/guest user on a doc restricted to workspace members, or a user from another workspace, or a user whose role was downgraded. Mentioning on a doc shared with a subset that excludes the target.","commonSituations":"Doc permission scope is narrower than the mention autocomplete source (autocomplete shows all workspace users, doc is member-restricted). Mentioning a user who left the workspace. Cross-workspace mention attempts.","solutions":["Source the mention autocomplete from users who can read the doc (intersect with the doc's readable-users list) rather than all workspace members.","Offer to grant Doc.Read to the mentioned user as part of the mention action, then retry.","On this error, prompt the author to share the doc with the user first.","Surface the docId from the error payload so the client can show which doc is the blocker."],"exampleFix":"// before\nif (!(await this.ac.user(parsedInput.userId).doc(ws, doc).can('Doc.Read'))) {\n  throw new MentionUserDocAccessDenied({ docId: parsedInput.body.doc.id });\n}\n\n// client side — only offer mentionable users who can already read the doc\nconst mentionable = await fetchUsersWhoCanRead(ws, doc.id);\n// or: grant read as part of the mention flow\nawait grantDocRead(ws, doc.id, mentionedUserId);\nawait mention(...);","handlingStrategy":"validation","validationCode":"async function assertMentionCanRead(ac, ws, doc, targetId) {\n  const ok = await ac.user(targetId).doc(ws, doc).can('Doc.Read');\n  if (!ok) throw new UserError(`User ${targetId} cannot read doc ${doc}; share first`);\n}","typeGuard":"function isMentionAccessDenied(e: unknown): boolean {\n  return e instanceof Error && (e as any).code === 'mention_user_doc_access_denied';\n}","tryCatchPattern":"try {\n  await resolver.createMention(me, input);\n} catch (e) {\n  if (isMentionAccessDenied(e)) {\n    return promptShareDoc(e.docId); // offer to grant Doc.Read, then retry\n  }\n  throw e;\n}","preventionTips":["Source mention candidates from users who can read the doc.","Offer to grant Doc.Read as part of the mention action.","Use the docId in the error payload to show which doc is blocked."],"tags":["notification","mention","permission","authorization","business-rule"],"backgroundTag":null,"analyzedSha":"26c515e050211269e911f7d9cfe162a26c83ed98","analyzedAt":"2026-08-12T13:15:16.447Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}