langgenius/dify · critical · ValueError
Workspace owner not found for tenant={current_tenant_id}
Error message
Workspace owner not found for tenant={current_tenant_id} What it means
ValueError raised inside _iter_tenant_member_batches' flush_current_tenant closure when a tenant's accumulated rows contain no member whose role equals TenantAccountRole.OWNER.value. Every workspace is expected to have exactly one owner; without one, the migration cannot determine the operator account that must authorize member-role replacements.
Source
Thrown at api/commands/rbac.py:102
select(TenantAccountJoin.tenant_id, TenantAccountJoin.account_id, TenantAccountJoin.role)
.order_by(TenantAccountJoin.tenant_id.asc(), TenantAccountJoin.id.asc())
.execution_options(yield_per=db_batch_size)
)
if tenant_id:
stmt = stmt.where(TenantAccountJoin.tenant_id == tenant_id)
current_tenant_id: str | None = None
owner_account_id: str | None = None
batches: list[list[tuple[str, str]]] = []
batch: list[tuple[str, str]] = []
def flush_current_tenant() -> Iterator[tuple[str, str, list[tuple[str, str]]]]:
if current_tenant_id is None:
return
if batch:
batches.append(batch.copy())
if not owner_account_id:
raise ValueError(f"Workspace owner not found for tenant={current_tenant_id}")
for item in batches:
yield current_tenant_id, owner_account_id, item
for row in session.execute(stmt):
workspace_id = str(row.tenant_id)
if current_tenant_id is not None and workspace_id != current_tenant_id:
yield from flush_current_tenant()
owner_account_id = None
batches = []
batch = []
current_tenant_id = workspace_id
account_id = str(row.account_id)
role = str(row.role)
if role == TenantAccountRole.OWNER.value:
owner_account_id = account_id
batch.append((account_id, role))
if len(batch) >= api_batch_size:
batches.append(batch)View on GitHub (pinned to ef8544b173)
Solutions
- Query `SELECT * FROM tenant_account_joins WHERE tenant_id='<id>' AND role='owner';` to confirm the owner row is missing.
- Restore or designate an owner for the tenant before re-running the migration.
- If the workspace is defunct, exclude its tenant_id via the command's --tenant-id filter.
- Audit for role string drift (e.g. 'Owner' vs 'owner') caused by case-sensitive comparisons.
Example fix
-- before -- SELECT role, count(*) FROM tenant_account_joins WHERE tenant_id='t1' GROUP BY role; -- admin | 2 -- normal | 5 -- after - restore owner UPDATE tenant_account_joins SET role='owner' WHERE tenant_id='t1' AND account_id='<owner-acct>';
Defensive patterns
Strategy: validation
Validate before calling
def tenant_has_owner(session, tenant_id: str) -> bool:
from sqlalchemy import select, func
return bool(session.scalar(
select(func.count()).select_from(TenantAccountJoin)
.where(TenantAccountJoin.tenant_id == tenant_id,
TenantAccountJoin.role == TenantAccountRole.OWNER.value)
)) Type guard
def tenant_has_owner(session, tenant_id: str) -> bool:
return session.scalars(
select(TenantAccountJoin).where(
TenantAccountJoin.tenant_id == tenant_id,
TenantAccountJoin.role == TenantAccountRole.OWNER.value,
).limit(1)
).first() is not None Try / catch
try:
for batch in _iter_tenant_member_batches(...):
process(batch)
except ValueError as exc:
if "Workspace owner not found" in str(exc):
click.echo(f"Orphaned workspace: {exc}. Restoring owner or excluding tenant.", err=True)
raise Prevention
- Preflight each tenant for an owner row before running the migration.
- Never delete TenantAccountJoin owner rows without transferring ownership.
- Use --tenant-id to skip orphaned workspaces.
- Audit role strings for case drift ('Owner' vs 'owner').
When it happens
Trigger: Triggered when, for a given tenant_id, none of the streamed TenantAccountJoin rows have role == 'owner'. This is evaluated at tenant-boundary flush time.
Common situations: The owner row was deleted from TenantAccountJoin (orphaned workspace), the owner's role string was changed to a non-owner value via a bad migration, or data corruption removed the ownership binding.
Related errors
- Unsupported legacy dataset permission: {permission}
- Builtin RBAC role not found for tenant={tenant_id}, legacy_r
- Unsupported legacy workspace role: {legacy_role}
- Dataset Collection Binding not found
- Vector store {vector_type} is not supported.
AI-assisted analysis of langgenius/dify@ef8544b173 (2026-08-12).
Data as JSON: /api/errors/6d6d759f4e6e942f.
Report an issue: GitHub.