{"record":{"id":"eb7485d51aa126fd","repo":"toeverything/AFFiNE","slug":"space-access-denied-eb7485","errorCode":"space_access_denied","errorMessage":"You do not have permission to access Space ${spaceId}.","messagePattern":"You do not have permission to access Space (.+?)\\.","errorType":"http","errorClass":"SpaceAccessDenied","httpStatus":403,"severity":"error","filePath":"packages/backend/server/src/core/workspaces/resolvers/member.ts","lineNumber":845,"sourceCode":"      workspaceId,\n      user.id,\n      WorkspaceRole.Collaborator,\n      {\n        status: WorkspaceMemberStatus.UnderReview,\n        source: WorkspaceMemberSource.Link,\n        inviterId: inviter.id,\n      }\n    );\n\n    await this.workspaceService.sendReviewRequestNotification(role.id);\n    this.event.emit('workspace.members.updated', { workspaceId });\n    return;\n  }\n\n  private async assertWorkspaceAcceptsMemberChange(workspaceId: string) {\n    const state = await this.policy.getWorkspaceState(workspaceId);\n    if (state.isReadonly) {\n      throw new SpaceAccessDenied({ spaceId: workspaceId });\n    }\n  }\n\n  private async allocateAvailableTeamSeats(workspaceId: string, limit: number) {\n    if (limit <= 0) return;\n    await this.workspaceService.allocateSeats(workspaceId, limit);\n  }\n}\n","sourceCodeStart":827,"sourceCodeEnd":854,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/26c515e050211269e911f7d9cfe162a26c83ed98/packages/backend/server/src/core/workspaces/resolvers/member.ts#L827-L854","documentation":"Thrown by the private `assertWorkspaceAcceptsMemberChange` helper when `policy.getWorkspaceState(workspaceId)` reports the workspace as readonly (`isReadonly`). A readonly workspace blocks all membership mutations (accept invite, etc.). Coded `space_access_denied` (no_permission) with `{ spaceId }`. It guards both `acceptInvitationByEmail` and `acceptInvitationByLink`.","triggerScenarios":"Accepting any invitation to a workspace whose policy state is locked — e.g. subscription lapsed/billing failure, the workspace was suspended for abuse, or an admin manually froze the workspace.","commonSituations":"Workspace on a plan whose billing failed and entered a grace/readonly period; a workspace suspended by the platform; trial expired into a read-only tier; an internal freeze flag flipped by ops.","solutions":["Restore the workspace's subscription / billing so the policy state flips out of readonly.","Contact the workspace owner or platform support to lift the readonly/frozen state, then retry the invite.","Check `getWorkspaceState` to confirm `isReadonly` before retrying acceptance.","Do not retry in a tight loop — readonly state is only cleared by an external action (payment/support)."],"exampleFix":"// before\nawait sdk.acceptInviteById({ inviteId });\n\n// after\nconst state = await sdk.getWorkspaceState({ workspaceId });\nif (state.isReadonly) {\n  notify('This workspace is currently locked. Ask the owner to restore the subscription.');\n  return;\n}\nawait sdk.acceptInviteById({ inviteId });","handlingStrategy":"try-catch","validationCode":"// Check workspace state before accepting\nconst state = await sdk.getWorkspaceState({ workspaceId });\nif (state.isReadonly) {\n  notify('This workspace is locked. Ask the owner to restore the subscription.');\n  return;\n}","typeGuard":"function isWorkspaceLocked(state) {\n  return Boolean(state && state.isReadonly);\n}","tryCatchPattern":"try {\n  await sdk.acceptInviteById({ inviteId });\n} catch (e) {\n  if (e.code === 'space_access_denied') {\n    notify('This workspace is currently locked. Please try again later.');\n  } else throw e;\n}","preventionTips":["Keep workspace billing active to avoid the readonly state.","Surface locked state in the UI early via getWorkspaceState.","Do not retry automatically — readonly clears only via an external action."],"tags":["graphql","workspace","policy","billing","suspended","nestjs"],"backgroundTag":null,"analyzedSha":"26c515e050211269e911f7d9cfe162a26c83ed98","analyzedAt":"2026-08-12T13:15:16.447Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}