{"record":{"id":"af4e17ac2e189612","repo":"danny-avila/LibreChat","slug":"role-accessroleid-is-for-role-resourcetype-r","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":"api/server/services/PermissionService.js","lineNumber":113,"sourceCode":"      // User and Group IDs must be valid ObjectIds\n      throw new Error(`Invalid principal ID: ${principalId}`);\n    }\n\n    if (!resourceId || !mongoose.Types.ObjectId.isValid(resourceId)) {\n      throw new Error(`Invalid resource ID: ${resourceId}`);\n    }\n\n    validateResourceType(resourceType);\n\n    // Get the role to determine permission bits\n    const role = await db.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 db.grantPermission(\n      principalType,\n      principalId,\n      resourceType,\n      resourceId,\n      role.permBits,\n      grantedBy,\n      session,\n      role._id,\n    );\n  } catch (error) {\n    logger.error(`[PermissionService.grantPermission] Error: ${error.message}`);\n    throw error;\n  }\n};","sourceCodeStart":95,"sourceCodeEnd":131,"githubUrl":"https://github.com/danny-avila/LibreChat/blob/5ff282f9006c436e561de1afd39a481bea1ef0d8/api/server/services/PermissionService.js#L95-L131","documentation":"Thrown by PermissionService.grantPermission when the resolved role exists but role.resourceType differs from the requested resourceType. Each access role is scoped to one resource type (its permBits are meaningful only for that type), so granting an agent-scoped role against a prompt resource is rejected to prevent meaningless ACL entries.","triggerScenarios":"Passing AccessRoleIds.AGENT_VIEWER with resourceType: 'prompt', or any combination where the role identifier and resourceType come from independent sources and disagree. Common when the caller hard-codes one role constant but varies resourceType dynamically.","commonSituations":"Reusing a single role constant across a generic 'share' handler that accepts multiple resource types; copy-pasting a grant call for a new resource type without switching the role; UI defaulting the role dropdown to the agent value.","solutions":["Map each resourceType to its role identifier at the caller and pass the pair together (e.g. { 'agent': AccessRoleIds.AGENT_VIEWER, 'prompt': AccessRoleIds.PROMPT_VIEWER }).","Inspect the resolved role (db.findRoleByIdentifier) in a REPL to confirm its resourceType before wiring the grant call.","If the role genuinely should apply to the new resourceType, create and seed a new role document with that resourceType rather than reusing the existing one."],"exampleFix":"// before\nawait grantPermission({ resourceType, resourceId, accessRoleId: AccessRoleIds.AGENT_VIEWER, ... });\n\n// after\nconst roleByType = {\n  agent: AccessRoleIds.AGENT_VIEWER,\n  prompt: AccessRoleIds.PROMPT_VIEWER,\n};\nconst accessRoleId = roleByType[resourceType];\nif (!accessRoleId) throw new Error(`No role mapped for resourceType ${resourceType}`);\nawait grantPermission({ resourceType, resourceId, accessRoleId, ... });","handlingStrategy":"validation","validationCode":"const role = await db.findRoleByIdentifier(accessRoleId);\nif (role && role.resourceType !== resourceType) {\n  throw new Error(`Role ${accessRoleId} is for ${role.resourceType} resources, not ${resourceType}`);\n}","typeGuard":"const roleMatchesResource = (role, resourceType) => !!role && role.resourceType === resourceType;","tryCatchPattern":"try {\n  await grantPermission({ ..., accessRoleId, resourceType });\n} catch (err) {\n  if (err.message.includes('resources, not')) return res.status(400).json({ message: err.message });\n  throw err;\n}","preventionTips":["Keep a single source-of-truth map from resourceType to its role identifiers.","When adding a resourceType, add and seed its roles in the same change.","Unit-test grantPermission across all supported resource types."],"tags":["permissions","validation","roles","resource-type"],"backgroundTag":null,"analyzedSha":"5ff282f9006c436e561de1afd39a481bea1ef0d8","analyzedAt":"2026-08-12T21:38:08.145Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}