danny-avila/LibreChat · error · Error
Role ${accessRoleId} is for ${role.resourceType} resources,
Error message
Role ${accessRoleId} is for ${role.resourceType} resources, not ${resourceType} What it means
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.
Source
Thrown at api/server/services/PermissionService.js:113
// User and Group IDs must be valid ObjectIds
throw new Error(`Invalid principal ID: ${principalId}`);
}
if (!resourceId || !mongoose.Types.ObjectId.isValid(resourceId)) {
throw new Error(`Invalid resource ID: ${resourceId}`);
}
validateResourceType(resourceType);
// Get the role to determine permission bits
const role = await db.findRoleByIdentifier(accessRoleId);
if (!role) {
throw new Error(`Role ${accessRoleId} not found`);
}
// Ensure the role is for the correct resource type
if (role.resourceType !== resourceType) {
throw new Error(
`Role ${accessRoleId} is for ${role.resourceType} resources, not ${resourceType}`,
);
}
return await db.grantPermission(
principalType,
principalId,
resourceType,
resourceId,
role.permBits,
grantedBy,
session,
role._id,
);
} catch (error) {
logger.error(`[PermissionService.grantPermission] Error: ${error.message}`);
throw error;
}
};View on GitHub (pinned to 5ff282f900)
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.
Example fix
// before
await grantPermission({ resourceType, resourceId, accessRoleId: AccessRoleIds.AGENT_VIEWER, ... });
// after
const roleByType = {
agent: AccessRoleIds.AGENT_VIEWER,
prompt: AccessRoleIds.PROMPT_VIEWER,
};
const accessRoleId = roleByType[resourceType];
if (!accessRoleId) throw new Error(`No role mapped for resourceType ${resourceType}`);
await grantPermission({ resourceType, resourceId, accessRoleId, ... }); Defensive patterns
Strategy: validation
Validate before calling
const role = await db.findRoleByIdentifier(accessRoleId);
if (role && role.resourceType !== resourceType) {
throw new Error(`Role ${accessRoleId} is for ${role.resourceType} resources, not ${resourceType}`);
} Type guard
const roleMatchesResource = (role, resourceType) => !!role && role.resourceType === resourceType;
Try / catch
try {
await grantPermission({ ..., accessRoleId, resourceType });
} catch (err) {
if (err.message.includes('resources, not')) return res.status(400).json({ message: err.message });
throw err;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Role ${accessRoleId} is for ${role.resourceType} resources,
- Invalid role ID: ${principalId}
- Invalid role ID: ${principalId}
- Role ${accessRoleId} not found
- Invalid resourceType: ${resourceType}. Valid types: ${validT
AI-assisted analysis of danny-avila/LibreChat@5ff282f900 (2026-08-12).
Data as JSON: /api/errors/af4e17ac2e189612.
Report an issue: GitHub.