langgenius/dify · error · ValueError

Unsupported legacy workspace role: {legacy_role}

Error message

Unsupported legacy workspace role: {legacy_role}

What it means

ValueError raised by _resolve_builtin_role_id when the supplied legacy_role string is not a key in _LEGACY_ROLE_TO_BUILTIN_TAG (owner/admin/editor/normal/dataset_operator). This guards against unknown role values before attempting tenant-scoped lookup.

Source

Thrown at api/commands/rbac.py:64

    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(
    tenant_id: str | None,
    *,
    db_batch_size: int,
    api_batch_size: int,
) -> Iterator[tuple[str, str, list[tuple[str, str]]]]:
    """Yield legacy member roles in tenant-scoped API-sized batches.

    Rows are projected to primitive values and streamed from the database, so
    the command never materializes every TenantAccountJoin ORM object. The
    iterator only keeps one tenant's API-sized batches in memory while it
    finds that tenant's owner account.
    """
    with session_factory.create_session() as session:

View on GitHub (pinned to ef8544b173)

Solutions

  1. Inspect the actual legacy_role value being passed; query TenantAccountJoin.role distinct values for the tenant.
  2. If the role is legitimate, add its mapping to _LEGACY_ROLE_TO_BUILTIN_TAG with the correct builtin tag.
  3. If the role is corrupt data, clean the TenantAccountJoin rows or filter them out before migration.
  4. Add a regression test covering any new TenantAccountRole enum member.

Example fix

# before
_LEGACY_ROLE_TO_BUILTIN_TAG = {
    TenantAccountRole.OWNER.value: "owner",
    TenantAccountRole.ADMIN.value: "admin",
}
# legacy_role="dataset_operator" -> raises

# after
_LEGACY_ROLE_TO_BUILTIN_TAG = {
    TenantAccountRole.OWNER.value: "owner",
    TenantAccountRole.ADMIN.value: "admin",
    TenantAccountRole.DATASET_OPERATOR.value: "dataset_operator",
}
Defensive patterns

Strategy: type-guard

Validate before calling

def is_supported_legacy_role(role: str) -> bool:
    return role in _LEGACY_ROLE_TO_BUILTIN_TAG

Type guard

def is_supported_legacy_role(role: str) -> bool:
    return role in _LEGACY_ROLE_TO_BUILTIN_TAG

Try / catch

try:
    role_id = _resolve_builtin_role_id(tenant_id, operator_account_id, legacy_role)
except ValueError as exc:
    click.echo(f"Skipping unsupported role: {exc}", err=True)
    return

Prevention

When it happens

Trigger: Triggered when _resolve_builtin_role_id is called with a legacy_role value not present in the TenantAccountRole enum values mapped in _LEGACY_ROLE_TO_BUILTIN_TAG.

Common situations: A new role was added to TenantAccountRole but not to _LEGACY_ROLE_TO_BUILTIN_TAG, or a corrupt/custom role string exists in TenantAccountJoin.role in the database.

Related errors


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