toeverything/AFFiNE · warning · CanNotRevokeYourself

can_not_revoke_yourself

can_not_revoke_yourself

Error message

You can not revoke your own permission.

What it means

Thrown by the revokeMember mutation when the caller passes their own user id — you cannot revoke your own membership with this API. The self-check runs first, before the member lookup and permission assertions, so it fires even if you are not otherwise authorized. Code can_not_revoke_yourself, type action_forbidden, HTTP 403.

Solutions

  1. Skip or disable the revoke action on the current user's row in the UI (show 'Leave workspace' instead if that is the intent).
  2. In scripts, filter out the acting user's id before calling revokeMember.
  3. If the goal is to leave the workspace, use the leave/transfer flow — owners must transfer ownership first (owner cannot leave).

Example fix

// before
for (const m of members) {
  await revokeMember(ws.id, m.id); // includes me -> can_not_revoke_yourself
}

// after
for (const m of members.filter(m => m.id !== me.id)) {
  await revokeMember(ws.id, m.id);
}
Defensive patterns

Strategy: validation

Validate before calling

// never point revoke at yourself
if (targetUserId === me.id) {
  disableRevokeButton(); // or route to a Leave-workspace flow instead
}

Type guard

function isCanNotRevokeYourself(e: unknown): boolean {
  const ext = (e as { extensions?: Record<string, unknown> })?.extensions;
  return String(ext?.name ?? '').toLowerCase() === 'can_not_revoke_yourself';
}

Try / catch

try {
  await revokeMember(workspaceId, targetUserId);
} catch (e) {
  if (isCanNotRevokeYourself(e)) {
    // row action bound to the wrong id — hide remove on the 'you' row
  } else throw e;
}

Prevention

When it happens

Trigger: Calling revokeMember(workspaceId, me.id) — typically a member-management UI whose row action is accidentally bound to the current user's row, or an automated cleanup script iterating all member ids including the operator's.

Common situations: Remove buttons not disabled on the 'you' row in the members table; batch scripts that revoke everyone then re-add, forgetting to skip the caller; owners trying to leave via revoke instead of a leave-workspace flow.

Related errors


AI-assisted analysis of toeverything/AFFiNE@4953682779 (2026-08-18). Data as JSON: /api/errors/5bcfdf435f493b56. Report an issue: GitHub.

Appendix: source

Thrown at packages/backend/server/src/core/workspaces/resolvers/member.ts:602

        inviteeId
      );
      status = invitation?.status;
    } else {
      const invitation = await this.models.workspaceUser.getById(inviteId);
      status = invitation?.status;
    }

    return { workspace, user: owner, invitee, status };
  }

  @Mutation(() => Boolean)
  async revokeMember(
    @CurrentUser() me: CurrentUser,
    @Args('workspaceId') workspaceId: string,
    @Args('userId') userId: string
  ) {
    if (userId === me.id) {
      throw new CanNotRevokeYourself();
    }

    const role = await this.models.workspaceUser.get(workspaceId, userId);

    if (!role) {
      throw new MemberNotFoundInSpace({ spaceId: workspaceId });
    }

    await this.ac
      .user(me.id)
      .workspace(workspaceId)
      .assert(
        role.type === WorkspaceRole.Admin
          ? 'Workspace.Administrators.Manage'
          : 'Workspace.Users.Manage'
      );

    await this.models.workspaceUser.delete(workspaceId, userId);

View on GitHub (pinned to 4953682779)