toeverything/AFFiNE · error · SpaceAccessDenied

space_access_denied

space_access_denied

Error message

You do not have permission to access Space ${spaceId}.

What it means

Thrown by assertWorkspaceAcceptsMemberChange when policy.getWorkspaceState(workspaceId).isReadonly is true. Member-changing operations (accepting invitations, join/leave) are blocked while the workspace is in a readonly policy state (e.g. billing suspended or admin-locked).

Solutions

  1. Resolve the underlying state: the workspace owner should fix billing/subscription so the workspace exits readonly.
  2. Retry the invitation acceptance after the workspace state returns to normal (the check re-evaluates each call).
  3. If you believe the state is wrong, inspect the workspace policy/quota state records for stuck flags.
Defensive patterns

Strategy: retry

Try / catch

try {
  await acceptInvitation(inviteId);
} catch (e) {
  if (getErrorCode(e) === 'space_access_denied') {
    // workspace is locked/readonly — have the owner fix billing, then the user can retry
    showNotice('This workspace is currently read-only. Ask the owner to resolve billing, then retry.');
  }
}

Prevention

When it happens

Trigger: Calling acceptInvitation (email or link path) or other member mutations on a workspace whose policy state is readonly.

Common situations: Self-hosted or cloud workspace suspended for overdue payment/expired trial; admin put the workspace in a locked state; the user retries an invitation accept while the lock is still active.

Related errors


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

Appendix: source

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

      workspaceId,
      user.id,
      WorkspaceRole.Collaborator,
      {
        status: WorkspaceMemberStatus.UnderReview,
        source: WorkspaceMemberSource.Link,
        inviterId: inviter.id,
      }
    );

    await this.workspaceService.sendReviewRequestNotification(role.id);
    this.event.emit('workspace.members.updated', { workspaceId });
    return;
  }

  private async assertWorkspaceAcceptsMemberChange(workspaceId: string) {
    const state = await this.policy.getWorkspaceState(workspaceId);
    if (state.isReadonly) {
      throw new SpaceAccessDenied({ spaceId: workspaceId });
    }
  }

  private async allocateAvailableTeamSeats(workspaceId: string, limit: number) {
    if (limit <= 0) return;
    await this.workspaceService.allocateSeats(workspaceId, limit);
  }
}

View on GitHub (pinned to 4953682779)