toeverything/AFFiNE · error · MemberNotFoundInSpace

member_not_found_in_space

member_not_found_in_space

Error message

Member not found in Space ${spaceId}.

What it means

Thrown by the invite-accept/approve mutation when models.workspaceUser.get(workspaceId, userId) returns no role record - the member row does not exist for that user/workspace pair. Reported as member_not_found_in_space (action_forbidden, HTTP 403).

Source

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

              userId,
              WorkspaceMemberStatus.Accepted
            );
          }
        }

        this.event.emit('workspace.members.updated', {
          workspaceId,
        });

        await this.workspaceService.sendReviewApprovedNotification(
          role.id,
          me.id
        );
        await this.policy.reconcileWorkspaceQuotaState(workspaceId);
      }
      return true;
    } else {
      throw new MemberNotFoundInSpace({ spaceId: workspaceId });
    }
  }

  @Mutation(() => Boolean)
  async grantMember(
    @CurrentUser() user: CurrentUser,
    @Args('workspaceId') workspaceId: string,
    @Args('userId') userId: string,
    @Args('permission', { type: () => WorkspaceRole }) newRole: WorkspaceRole
  ) {
    await this.ac
      .user(user.id)
      .workspace(workspaceId)
      .assert(
        newRole === WorkspaceRole.Owner
          ? 'Workspace.TransferOwner'
          : 'Workspace.Users.Manage'
      );

View on GitHub (pinned to 26c515e050)

Solutions

  1. Verify the userId actually has a pending/UnderReview membership in that workspace before accepting.
  2. Re-issue the invite to create a fresh membership row.
  3. Check the workspaceId/userId pairing - membership is scoped per workspace.
Defensive patterns

Strategy: validation

Validate before calling

const membership = await getWorkspaceUser(workspaceId, userId);
if (!membership) throw new Error('No membership record; request a new invite');
// only then accept/approve

Type guard

function hasMembership(row: unknown): row is { status: string } {
  return !!row && typeof (row as any).status === 'string';
}

Try / catch

try {
  await acceptInvite(workspaceId);
} catch (e) {
  if (isGraphQLError(e, 'member_not_found_in_space')) showInvalidInviteNotice();
  else throw e;
}

Prevention

When it happens

Trigger: Calling accept/approve for a userId that has no workspaceUser row in the given workspace (never invited, already removed, or wrong id).

Common situations: Stale invite link after the membership was revoked; wrong userId passed (typo or cross-workspace id); the member was already removed by an admin.

Related errors


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