danny-avila/LibreChat · error · Error

Invalid role ID: ${principalId}

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.

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)

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.