danny-avila/LibreChat · error · Error
Role ${accessRoleId} not found
Error message
Role ${accessRoleId} not found What it means
Thrown by PermissionService.grantPermission after db.findRoleByIdentifier(accessRoleId) returns null. The access role identifier passed in does not resolve to any document in the roles collection, so the service cannot determine the permBits to grant. Roles are expected to be referenced by a known identifier (e.g. AccessRoleIds.AGENT_VIEWER) and must exist and be seeded beforehand.
Source
Thrown at api/server/services/PermissionService.js:108
} else if (
principalType &&
principalType !== PrincipalType.PUBLIC &&
!mongoose.Types.ObjectId.isValid(principalId)
) {
// 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,
);View on GitHub (pinned to 5ff282f900)
Solutions
- Use the AccessRoleIds enum/constant for accessRoleId instead of a hand-typed string, so a typo becomes a compile/lint error.
- Run the role-seeding migration/script for the environment and verify db.findRoleByIdentifier(AccessRoleIds.AGENT_VIEWER) returns a document.
- Log accessRoleId and role.resourceType at the call site to confirm the expected identifier is being sent.
- If you extended resource types, ensure the matching role documents for the new resourceType were inserted.
Example fix
// before
await grantPermission({ ..., accessRoleId: 'AGENT_READ' });
// after
const { AccessRoleIds } = require('./roleConstants');
await grantPermission({ ..., accessRoleId: AccessRoleIds.AGENT_VIEWER }); Defensive patterns
Strategy: validation
Validate before calling
const role = await db.findRoleByIdentifier(accessRoleId);
if (!role) {
throw new Error(`Role ${accessRoleId} not found. Has the role-seeding migration run?`);
} Type guard
const isKnownAccessRole = (id) => Object.values(AccessRoleIds).includes(id);
Try / catch
try {
await grantPermission({ ..., accessRoleId });
} catch (err) {
if (err.message.startsWith('Role ') && err.message.endsWith(' not found')) {
return res.status(400).json({ message: 'Unknown access role.' });
}
throw err;
} Prevention
- Reference access roles only via the AccessRoleIds enum.
- Include a role-seeding step in deploy/migration scripts and assert it succeeded.
- Add a startup health check that verifies expected roles exist.
When it happens
Trigger: Passing a typo'd or invented accessRoleId (e.g. 'AGENT_READ'), passing a role _id where an identifier string is expected, or running against a database where the role-seeding migration has not executed. Also triggered after a roles collection reset/wipe without re-seeding.
Common situations: Fresh deploy where the access-roles seed script did not run; renaming a role identifier in code without a migration; environments sharing a DB where another tenant wiped roles; tests against a mock db that does not implement findRoleByIdentifier.
Related errors
- Forbidden: Insufficient MCP server permissions
- Invalid role ID: ${principalId}
- Role ${accessRoleId} is for ${role.resourceType} resources,
- Invalid role ID: ${principalId}
- Role ${accessRoleId} not found
AI-assisted analysis of danny-avila/LibreChat@5ff282f900 (2026-08-12).
Data as JSON: /api/errors/f0bbb9da734d58d6.
Report an issue: GitHub.