danny-avila/LibreChat · error · Error

Role ${accessRoleId} not found

Error message

Role ${accessRoleId} not found

What it means

grantPermission looks up the role by its identifier via findRoleByIdentifier(accessRoleId). If no role document matches that identifier (e.g. AccessRoleIds.AGENT_VIEWER), the grant aborts because permBits cannot be determined. This is a referential-integrity check against the access roles collection.

Source

Thrown at packages/api/src/acl/accessControlService.ts:93

      } else if (
        principalType &&
        principalType !== PrincipalType.PUBLIC &&
        (!principalId || !Types.ObjectId.isValid(principalId))
      ) {
        // User and Group IDs must be valid ObjectIds
        throw new Error(`Invalid principal ID: ${principalId}`);
      }

      if (!resourceId || !Types.ObjectId.isValid(resourceId)) {
        throw new Error(`Invalid resource ID: ${resourceId}`);
      }

      this.validateResourceType(resourceType as ResourceType);

      // Get the role to determine permission bits
      const role = await this._dbMethods.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 this._dbMethods.grantPermission(
        principalType,
        principalId,
        resourceType,
        resourceId,
        role.permBits,
        grantedBy,
        session,
        role._id,
        expiredAt,

View on GitHub (pinned to 5ff282f900)

Solutions

  1. Run the access-role seed/migration so the role exists in the database.
  2. Confirm the accessRoleId matches a value from AccessRoleIds exported by librechat-data-provider.
  3. Verify the role's resourceType matches the resource you are granting on (see error 207).

Example fix

// before
await grantPermission({ ..., accessRoleId: 'AgentViewer' }); // wrong casing / display name

// after
import { AccessRoleIds } from 'librechat-data-provider';
await grantPermission({ ..., accessRoleId: AccessRoleIds.AGENT_VIEWER });
Defensive patterns

Strategy: try-catch

Validate before calling

import { AccessRoleIds } from 'librechat-data-provider';
const knownRoleIds = new Set(Object.values(AccessRoleIds));
function assertAccessRole(v: string) {
  if (!knownRoleIds.has(v)) throw new Error(`Unknown accessRoleId: ${v}`);
  return v;
}

Type guard

import { AccessRoleIds } from 'librechat-data-provider';
const isAccessRoleId = (v: unknown): v is string =>
  typeof v === 'string' && Object.values(AccessRoleIds).includes(v as AccessRoleIds);

Try / catch

try {
  await svc.grantPermission({ ..., accessRoleId });
} catch (error) {
  if (error instanceof Error && error.message.startsWith('Role ') && error.message.endsWith(' not found')) {
    return res.status(400).json({ error: 'Unknown role; run the access-role seed.' });
  }
  throw error;
}

Prevention

When it happens

Trigger: Passing an accessRoleId that does not exist in the roles collection; using a role identifier from a different resource type; a roles seed/migration that has not been run; a typo in the AccessRoleIds literal.

Common situations: Fresh database where role seeding was skipped; a renamed role identifier after an upgrade; passing the role's display name instead of its identifier; multi-tenant systems where the role belongs to another tenant.

Related errors


AI-assisted analysis of danny-avila/LibreChat@5ff282f900 (2026-08-12). Data as JSON: /api/errors/c58f895ab655e39f. Report an issue: GitHub.