toeverything/AFFiNE · error · ExpectToUpdateDocUserRole

expect_to_update_doc_user_role

expect_to_update_doc_user_role

Error message

Expect doc not to be workspace

What it means

Thrown by the updateDocUserRole mutation when input.docId === input.workspaceId. Same root-doc invariant as revokeDocUserRole: the workspace root doc is addressed by workspace-level role APIs, not the doc-grant API. Rejecting early (invalid_input, HTTP 400) prevents a no-op or inconsistent state where doc-level owner/grant logic would run against the workspace root.

Source

Thrown at packages/backend/server/src/core/workspaces/resolvers/doc.ts:810

    this.logger.log(`Revoke doc user roles (${JSON.stringify(info)})`);
    return true;
  }

  @Mutation(() => Boolean)
  async updateDocUserRole(
    @CurrentUser() user: CurrentUser,
    @Args('input') input: UpdateDocUserRoleInput
  ): Promise<boolean> {
    const pairs = {
      spaceId: input.workspaceId,
      docId: input.docId,
    };
    if (input.workspaceId === input.docId) {
      this.logger.error(
        'Expect to update doc user role, but it is a workspace',
        pairs
      );
      throw new ExpectToUpdateDocUserRole(
        pairs,
        'Expect doc not to be workspace'
      );
    }

    const info = {
      ...pairs,
      userId: input.userId,
      role: input.role,
    };

    if (input.role === DocRole.Owner) {
      await this.ac.user(user.id).doc(input).assert('Doc.TransferOwner');
      await this.models.docUser.setOwner(
        input.workspaceId,
        input.docId,
        input.userId
      );

View on GitHub (pinned to 26c515e050)

Solutions

  1. Supply the actual nested doc id as docId; keep workspaceId as the workspace id.
  2. To change a workspace member's role, call grantMember(workspaceId, userId, role) instead.
  3. Guard the caller: skip/redirect when workspaceId === docId.

Example fix

// before
updateDocUserRole({ workspaceId: ws.id, docId: ws.id, userId, role });
// after
updateDocUserRole({ workspaceId: ws.id, docId: page.id, userId, role });
Defensive patterns

Strategy: validation

Validate before calling

function assertUpdateDocUserRoleArgs(input: { workspaceId: string; docId: string }) {
  if (input.workspaceId === input.docId) {
    throw new Error('Cannot update doc role on the workspace root; use grantMember instead');
  }
}
assertUpdateDocUserRoleArgs(input);

Type guard

function isNestedDocInput(input: { workspaceId: string; docId: string }): boolean {
  return Boolean(input.workspaceId) && Boolean(input.docId) && input.workspaceId !== input.docId;
}

Prevention

When it happens

Trigger: Calling mutation updateDocUserRole with UpdateDocUserRoleInput whose docId equals workspaceId (the workspace root doc id passed as docId).

Common situations: A shared permission-management component binds both fields to the same workspace id when the user opens the workspace landing page; a script that iterates docs and accidentally includes the workspace root id; stale cached doc id equated to the workspace id.

Related errors


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