{"record":{"id":"7e57cc2c61463e8a","repo":"danny-avila/LibreChat","slug":"role-accessroleid-is-for-role-resourcetype-r-7e57cc","errorCode":null,"errorMessage":"Role ${accessRoleId} is for ${role.resourceType} resources, not ${resourceType}","messagePattern":"Role (.+?) is for (.+?) resources, not (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/api/src/acl/accessControlService.ts","lineNumber":98,"sourceCode":"        // User and Group IDs must be valid ObjectIds\n        throw new Error(`Invalid principal ID: ${principalId}`);\n      }\n\n      if (!resourceId || !Types.ObjectId.isValid(resourceId)) {\n        throw new Error(`Invalid resource ID: ${resourceId}`);\n      }\n\n      this.validateResourceType(resourceType as ResourceType);\n\n      // Get the role to determine permission bits\n      const role = await this._dbMethods.findRoleByIdentifier(accessRoleId);\n      if (!role) {\n        throw new Error(`Role ${accessRoleId} not found`);\n      }\n\n      // Ensure the role is for the correct resource type\n      if (role.resourceType !== resourceType) {\n        throw new Error(\n          `Role ${accessRoleId} is for ${role.resourceType} resources, not ${resourceType}`,\n        );\n      }\n      return await this._dbMethods.grantPermission(\n        principalType,\n        principalId,\n        resourceType,\n        resourceId,\n        role.permBits,\n        grantedBy,\n        session,\n        role._id,\n        expiredAt,\n      );\n    } catch (error) {\n      logger.error(\n        `[PermissionService.grantPermission] Error: ${error instanceof Error ? error.message : ''}`,\n        error,","sourceCodeStart":80,"sourceCodeEnd":116,"githubUrl":"https://github.com/danny-avila/LibreChat/blob/5ff282f9006c436e561de1afd39a481bea1ef0d8/packages/api/src/acl/accessControlService.ts#L80-L116","documentation":"Even after a role is found, grantPermission enforces that role.resourceType equals the requested resourceType. A role defined for 'agent' resources cannot be applied to a 'prompt' resource, preventing permission bits from being misapplied across resource kinds.","triggerScenarios":"Pairing an agent-scoped role (e.g. AGENT_VIEWER) with resourceType: ResourceType.PROMPT, or any cross-kind mismatch between the role document and the grant request.","commonSituations":"UI lets the user pick a role and a resource independently and they are mismatched; copy-pasting a grant call and changing resourceType but not accessRoleId; adding a new ResourceType without defining roles for it.","solutions":["Use a role whose resourceType matches the resource being granted on (e.g. AGENT_VIEWER for ResourceType.AGENT).","Drive role selection from the resourceType in the UI so only compatible roles are offered.","Add a guard in the caller: assert role definitions exist for every ResourceType you grant on."],"exampleFix":"// before\nawait grantPermission({\n  resourceType: ResourceType.PROMPT,\n  accessRoleId: AccessRoleIds.AGENT_VIEWER, // role.resourceType === 'agent'\n  ...,\n});\n\n// after\nawait grantPermission({\n  resourceType: ResourceType.PROMPT,\n  accessRoleId: AccessRoleIds.PROMPT_VIEWER, // role.resourceType === 'prompt'\n  ...,\n});","handlingStrategy":"validation","validationCode":"// ensure role.resourceType matches the resourceType before granting\nif (role.resourceType !== resourceType) {\n  throw new Error(`Role ${accessRoleId} is for ${role.resourceType}, not ${resourceType}`);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Drive role selection from the chosen resourceType in the UI so only compatible roles are selectable.","Pair each ResourceType with its own role identifiers in a lookup table.","Add an integration test per (resourceType, role) pair."],"tags":["acl","validation","permissions","roles","resource-type"],"backgroundTag":null,"analyzedSha":"5ff282f9006c436e561de1afd39a481bea1ef0d8","analyzedAt":"2026-08-12T21:38:08.145Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}