toeverything/AFFiNE · error · ExpectToRevokeDocUserRoles
expect_to_revoke_doc_user_roles
expect_to_revoke_doc_user_roles
Error message
Expect doc not to be workspace
What it means
Thrown by the revokeDocUserRole GraphQL mutation when input.docId === input.workspaceId. In this codebase a workspace's root doc shares the workspace's id, so the per-doc grant APIs do not apply to it; access to the root is governed entirely by workspace membership. The guard exists because calling revokeDocUserRole on the workspace root would silently no-op or corrupt workspace-level permissions, so it is rejected up front as invalid_input (HTTP 400).
Source
Thrown at packages/backend/server/src/core/workspaces/resolvers/doc.ts:771
this.logger.log(`Grant doc user roles (${JSON.stringify(info)})`);
return true;
}
@Mutation(() => Boolean)
async revokeDocUserRoles(
@CurrentUser() user: CurrentUser,
@Args('input') input: RevokeDocUserRoleInput
): Promise<boolean> {
const pairs = {
spaceId: input.workspaceId,
docId: input.docId,
};
if (input.workspaceId === input.docId) {
this.logger.error(
'Expect to revoke doc user roles, but it is a workspace',
pairs
);
throw new ExpectToRevokeDocUserRoles(
pairs,
'Expect doc not to be workspace'
);
}
await this.ac.user(user.id).doc(input).assert('Doc.Users.Manage');
await this.models.docUser.delete(
input.workspaceId,
input.docId,
input.userId
);
this.event.emit('doc.grants.changed', {
workspaceId: input.workspaceId,
docId: input.docId,
});
const info = {
...pairs,View on GitHub (pinned to 26c515e050)
Solutions
- Pass the nested doc's id as docId and keep the workspace id in workspaceId.
- If you intend to remove a workspace member, call revokeMember(workspaceId, userId) instead.
- Add a client-side precondition: if (workspaceId === docId) route to the workspace-member flow rather than the doc-grant flow.
Example fix
// before
revokeDocUserRole({ workspaceId: ws.id, docId: ws.id, userId });
// after
revokeDocUserRole({ workspaceId: ws.id, docId: page.id, userId }); Defensive patterns
Strategy: validation
Validate before calling
function assertRevokeDocArgs(input: { workspaceId: string; docId: string }) {
if (!input.workspaceId || !input.docId) throw new Error('workspaceId and docId are required');
if (input.workspaceId === input.docId) {
throw new Error('Cannot revoke doc roles on the workspace root; use revokeMember instead');
}
}
// call before revokeDocUserRole
assertRevokeDocArgs(input); Type guard
function isNestedDocInput(input: { workspaceId: string; docId: string }): boolean {
return Boolean(input.workspaceId) && Boolean(input.docId) && input.workspaceId !== input.docId;
} Prevention
- Never pass the workspace id as docId; the workspace root doc shares the workspace id.
- Differentiate the workspace-member flow from the doc-grant flow in the UI.
- Unit-test the precondition workspaceId !== docId for all doc-grant calls.
When it happens
Trigger: Calling mutation revokeDocUserRole with RevokeDocUserRoleInput where docId and workspaceId are the same string value (e.g. passing the workspace id as docId).
Common situations: A frontend 'manage doc access' screen is reused for the workspace root page and fills both fields from the same workspace id; an older client that never split the two ids; copying a workspace id from the URL into both args of a manual GraphQL request.
Related errors
- expect_to_update_doc_user_role
- doc_default_role_can_not_be_owner
- action_forbidden_on_non_team_workspace
- expect_to_grant_doc_user_roles
- action_forbidden
AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12).
Data as JSON: /api/errors/57ce89b5f9cb667b.
Report an issue: GitHub.