danny-avila/LibreChat · error · Error

Invalid role ID

Error message

Invalid role ID: ${principalId}

What it means

When principalType is ROLE, grantPermission expects principalId to be a non-empty trimmed string (the role name). This branch rejects role ids that are not strings or are whitespace-only. Role principals are keyed by name, not ObjectId, unlike USER and GROUP.

Solutions

  1. Pass the role's logical identifier string (e.g. from AccessRoleIds or the role.name field), not its ObjectId.
  2. Trim and assert non-empty before calling grantPermission.
  3. Double-check principalType is genuinely ROLE; for USER/GROUP an ObjectId is correct.

Example fix

// before
await grantPermission({ principalType: PrincipalType.ROLE, principalId: roleDoc._id, ... });

// after
await grantPermission({ principalType: PrincipalType.ROLE, principalId: AccessRoleIds.MY_ROLE, ... });
Defensive patterns

Strategy: validation

Validate before calling

function assertRoleId(v: unknown): string {
  if (typeof v !== 'string' || v.trim().length === 0) {
    throw new Error(`Invalid role ID: ${String(v)}`);
  }
  return v;
}

Type guard

const isRoleId = (v: unknown): v is string =>
  typeof v === 'string' && v.trim().length > 0;

Prevention

When it happens

Trigger: grantPermission with principalType: PrincipalType.ROLE where principalId is a number, an ObjectId, undefined, or a blank string; passing a role document's _id instead of its identifier string.

Common situations: Confusing the role's Mongo _id with its logical identifier (AccessRoleIds); copy-paste from a USER grant that supplies an ObjectId; trimming logic upstream that produced an empty string.

Related errors


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

Appendix: source

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

      accessRoleId,
      grantedBy,
      session,
      expiredAt,
    } = args;
    try {
      if (!Object.values(PrincipalType).includes(principalType)) {
        throw new Error(`Invalid principal type: ${principalType}`);
      }

      if (principalType !== PrincipalType.PUBLIC && !principalId) {
        throw new Error('Principal ID is required for user, group, and role principals');
      }

      // Validate principalId based on type
      if (principalId && principalType === PrincipalType.ROLE) {
        // Role IDs are strings (role names)
        if (typeof principalId !== 'string' || principalId.trim().length === 0) {
          throw new Error(`Invalid role ID: ${principalId}`);
        }
      } 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);

View on GitHub (pinned to 5ff282f900)