toeverything/AFFiNE · error · ExpectToGrantDocUserRoles
expect_to_grant_doc_user_roles
expect_to_grant_doc_user_roles
Error message
Expect doc not to be workspace
What it means
grantDocUserRoles rejects input where workspaceId === docId: the workspace root is a Space, and doc-level user roles only apply to real pages. The check runs before Doc.Users.Manage is asserted, so this fires even for callers who would otherwise be authorized.
Solutions
- Validate input client-side: workspaceId and docId must differ before calling the mutation
- Source docId from a page/doc object, never from the workspace
- For workspace-wide roles use the workspace member APIs instead of doc roles
Example fix
// before
await grantDocUserRoles({ workspaceId: ws.id, docId: ws.id, userIds, role }); // root -> EXPECT_TO_GRANT_DOC_USER_ROLES
// after
const page = getSelectedPage(); // real page id
if (!page || page.id === ws.id) throw new Error('select a page first');
await grantDocUserRoles({ workspaceId: ws.id, docId: page.id, userIds, role }); Defensive patterns
Strategy: validation
Validate before calling
// Validate the grant input before calling the mutation
function validDocRoleInput(workspaceId: string, docId: string | undefined): boolean {
return !!docId && docId !== workspaceId;
}
if (!validDocRoleInput(input.workspaceId, input.docId)) {
throw new Error('docId must reference a page, not the workspace');
}
await grantDocUserRoles(input); Type guard
function isExpectToGrantDocUserRoles(e: unknown): boolean {
return (
typeof e === 'object' && e !== null &&
(e as { extensions?: { code?: string } }).extensions?.code === 'expect_to_grant_doc_user_roles'
);
} Try / catch
try {
await grantDocUserRoles(input);
} catch (e) {
if (isExpectToGrantDocUserRoles(e)) {
resetShareDialogToPageSelection(); // user targeted the root; ask for a page
} else throw e;
} Prevention
- Hide doc-sharing actions on the workspace root node
- Build mutation inputs from a selected page object, with docId required and non-defaulted
- Reuse one shared id guard across grant/revoke/update doc-role call sites
When it happens
Trigger: Calling grantDocUserRoles with input.docId equal to input.workspaceId — client building the input from a workspace/root object or defaulting docId to the workspace id.
Common situations: Sharing flows that treat the workspace root as a page; forms where docId was never populated and fell back to the space id.
Related errors
- doc_default_role_can_not_be_owner
- expect_to_revoke_doc_user_roles
- expect_to_update_doc_user_role
- expect_to_publish_doc
- expect_to_revoke_public_doc
AI-assisted analysis of toeverything/AFFiNE@2af30773ae (2026-08-18).
Data as JSON: /api/errors/686479db8dec8735.
Report an issue: GitHub.
Appendix: source
Thrown at packages/backend/server/src/core/workspaces/resolvers/doc.ts:748
);
}
@Mutation(() => Boolean)
async grantDocUserRoles(
@CurrentUser() user: CurrentUser,
@Args('input') input: GrantDocUserRolesInput
): Promise<boolean> {
const pairs = {
spaceId: input.workspaceId,
docId: input.docId,
};
if (input.workspaceId === input.docId) {
this.logger.error(
'Expect to grant doc user roles, but it is a workspace',
pairs
);
throw new ExpectToGrantDocUserRoles(
pairs,
'Expect doc not to be workspace'
);
}
const role = toDomainDocRole(input.role);
if (!role || role === 'owner') {
throw new ExpectToGrantDocUserRoles(pairs, 'Invalid grant role');
}
try {
await this.runtime.executeDomainCommandV1({
command: 'grant_doc_roles',
actorUserId: user.id,
workspaceId: input.workspaceId,
docId: input.docId,
targetUserIds: input.userIds,
newRole: role,
});View on GitHub (pinned to 2af30773ae)