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

  1. Use the AccessRoleIds enum/constant for accessRoleId instead of a hand-typed string, so a typo becomes a compile/lint error.
  2. Run the role-seeding migration/script for the environment and verify db.findRoleByIdentifier(AccessRoleIds.AGENT_VIEWER) returns a document.
  3. Log accessRoleId and role.resourceType at the call site to confirm the expected identifier is being sent.
  4. 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

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


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