langgenius/dify · critical · ValueError

Builtin RBAC role not found for tenant={tenant_id}, legacy_r

Error message

Builtin RBAC role not found for tenant={tenant_id}, legacy_role={legacy_role}

What it means

ValueError raised in _resolve_builtin_role_ids when one of the expected builtin RBAC role tags (owner, admin, editor, normal, dataset_operator) is not present among the tenant's global_system_default builtin roles returned by RBACService.Roles.list. Each legacy workspace role maps to an expected builtin tag; if that tag's role id is missing, the migration cannot map members.

Source

Thrown at api/commands/rbac.py:51

    identified by runtime ids, so the command must look them up per tenant.
    """
    roles = RBACService.Roles.list(
        tenant_id=tenant_id,
        account_id=operator_account_id,
        options=ListOption(page_number=1, results_per_page=100),
    ).data
    role_id_by_tag = {
        role.role_tag: role.id
        for role in roles
        if role.is_builtin and role.category == "global_system_default" and role.role_tag
    }
    resolved: dict[str, str] = {}
    for legacy_role, expected_builtin_tag in _LEGACY_ROLE_TO_BUILTIN_TAG.items():
        role_id = role_id_by_tag.get(expected_builtin_tag)
        if expected_builtin_tag == "dataset_operator" and not dify_config.DATASET_OPERATOR_ENABLED:
            continue
        if not role_id:
            raise ValueError(f"Builtin RBAC role not found for tenant={tenant_id}, legacy_role={legacy_role}")
        resolved[legacy_role] = role_id
    return resolved


def _resolve_builtin_role_id(tenant_id: str, operator_account_id: str, legacy_role: str) -> str:
    """Resolve a legacy workspace role to the current tenant's builtin RBAC role id.

    The migration replays the old `TenantAccountJoin.role` values onto the
    RBAC member-role binding API. Builtin RBAC roles are tenant-scoped and
    identified by runtime ids, so the command must look them up per tenant.
    """
    if legacy_role not in _LEGACY_ROLE_TO_BUILTIN_TAG:
        raise ValueError(f"Unsupported legacy workspace role: {legacy_role}")

    return _resolve_builtin_role_ids(tenant_id, operator_account_id)[legacy_role]


def _iter_tenant_member_batches(

View on GitHub (pinned to ef8544b173)

Solutions

  1. Ensure RBAC builtin role seeding ran for this tenant (check the roles API or DB for owner/admin/editor/normal tags).
  2. Verify the operator account used by the command has permission to list roles for the tenant.
  3. If DATASET_OPERATOR_ENABLED is false intentionally, confirm the failing role is not dataset_operator (which is expected to be skipped).
  4. If the tenant has >100 builtin roles, increase the ListOption results_per_page.

Example fix

# before - roles not seeded
flask rbac-migrate-member-roles  # raises

# after - seed builtin roles first
flask rbac-seed-builtin-roles && flask rbac-migrate-member-roles
Defensive patterns

Strategy: validation

Validate before calling

def builtin_roles_present(tenant_id, operator_account_id) -> bool:
    roles = RBACService.Roles.list(
        tenant_id=tenant_id, account_id=operator_account_id,
        options=ListOption(page_number=1, results_per_page=100),
    ).data
    tags = {r.role_tag for r in roles if r.is_builtin and r.category == "global_system_default"}
    required = set(_LEGACY_ROLE_TO_BUILTIN_TAG.values())
    if not dify_config.DATASET_OPERATOR_ENABLED:
        required.discard("dataset_operator")
    return required.issubset(tags)

Type guard

def has_required_builtin_tags(role_tags: set[str]) -> bool:
    required = set(_LEGACY_ROLE_TO_BUILTIN_TAG.values())
    if not dify_config.DATASET_OPERATOR_ENABLED:
        required.discard("dataset_operator")
    return required.issubset(role_tags)

Try / catch

try:
    role_ids = _resolve_builtin_role_ids(tenant_id, operator_account_id)
except ValueError as exc:
    click.echo(f"RBAC role catalog incomplete: {exc}", err=True)
    raise click.Abort()

Prevention

When it happens

Trigger: Triggered when the RBAC roles list for the tenant (page 1, 100 per page) lacks a role whose role_tag equals the expected builtin tag and whose is_builtin is true and category is 'global_system_default'. Also note dataset_operator is skipped when DATASET_OPERATOR_ENABLED is false.

Common situations: The tenant's builtin roles were not seeded (incomplete RBAC initialization), the role catalog changed in a version upgrade, pagination missed roles because there are more than 100 builtin roles, or the operator account lacks permission to list roles.

Related errors


AI-assisted analysis of langgenius/dify@ef8544b173 (2026-08-12). Data as JSON: /api/errors/cbac398bd726bb17. Report an issue: GitHub.