toeverything/AFFiNE · warning · MentionUserDocAccessDenied

mention_user_doc_access_denied

mention_user_doc_access_denied

Error message

Mentioned user can not access doc ${docId}.

What it means

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.

Source

Thrown at packages/backend/server/src/core/notification/resolver.ts:71

        createdByUserId: me.id,
      },
    });
    if (parsedInput.userId === me.id) {
      throw new MentionUserOneselfDenied();
    }
    // currentUser can update the doc
    await this.ac
      .user(me.id)
      .doc(parsedInput.body.workspaceId, parsedInput.body.doc.id)
      .assert('Doc.Update');
    // mention user can read the doc
    if (
      !(await this.ac
        .user(parsedInput.userId)
        .doc(parsedInput.body.workspaceId, parsedInput.body.doc.id)
        .can('Doc.Read'))
    ) {
      throw new MentionUserDocAccessDenied({
        docId: parsedInput.body.doc.id,
      });
    }
    const notification = await this.service.createMention(parsedInput);
    return notification.id;
  }

  @Mutation(() => Boolean, {
    description: 'mark notification as read',
  })
  async readNotification(
    @CurrentUser() me: UserType,
    @Args('id') notificationId: string
  ) {
    await this.service.markAsRead(me.id, notificationId);
    return true;
  }

View on GitHub (pinned to 26c515e050)

Solutions

  1. Source the mention autocomplete from users who can read the doc (intersect with the doc's readable-users list) rather than all workspace members.
  2. Offer to grant Doc.Read to the mentioned user as part of the mention action, then retry.
  3. On this error, prompt the author to share the doc with the user first.
  4. Surface the docId from the error payload so the client can show which doc is the blocker.

Example fix

// before
if (!(await this.ac.user(parsedInput.userId).doc(ws, doc).can('Doc.Read'))) {
  throw new MentionUserDocAccessDenied({ docId: parsedInput.body.doc.id });
}

// client side — only offer mentionable users who can already read the doc
const mentionable = await fetchUsersWhoCanRead(ws, doc.id);
// or: grant read as part of the mention flow
await grantDocRead(ws, doc.id, mentionedUserId);
await mention(...);
Defensive patterns

Strategy: validation

Validate before calling

async function assertMentionCanRead(ac, ws, doc, targetId) {
  const ok = await ac.user(targetId).doc(ws, doc).can('Doc.Read');
  if (!ok) throw new UserError(`User ${targetId} cannot read doc ${doc}; share first`);
}

Type guard

function isMentionAccessDenied(e: unknown): boolean {
  return e instanceof Error && (e as any).code === 'mention_user_doc_access_denied';
}

Try / catch

try {
  await resolver.createMention(me, input);
} catch (e) {
  if (isMentionAccessDenied(e)) {
    return promptShareDoc(e.docId); // offer to grant Doc.Read, then retry
  }
  throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12). Data as JSON: /api/errors/3a0a8be37de56e3b. Report an issue: GitHub.