Skip to content
GitHub

Troubleshooting

TenantResolutionError: Tenant resolution failed

Cause: All resolvers returned None — no header, JWT claim, subdomain, or path pattern matched.
Fix: Check the resolvers list and each resolver’s configuration. Verify the x-tenant-id header is being sent.

TenantNotFoundError(tenant_id="tenant-abc")

Cause: The resolver extracted a tenant ID, but the store has no matching record.
Fix: Verify the tenant was created via TenantProviderProtocol.create_tenant(). Check the store backend (in-memory is lost on restart).

TenantInactiveError(tenant_id="tenant-abc")
TenantSuspendedError(tenant_id="tenant-abc")

Cause: The tenant exists but is not ACTIVE.
Fix: Reactivate via provider.activate_tenant(tenant_id). For suspended tenants, resolve the billing/compliance issue first.

[no error — tenant not resolved]

Cause: TenantContextMiddleware was not registered, or lexigram-web is not installed.
Fix: Verify lexigram-web is installed and the middleware registry is available. Check boot logs for tenant_context_middleware_registered.

TenantProvisioningError: Tenant provisioning failed

Cause: The database user lacks CREATE SCHEMA permissions, or lexigram-sql is not installed.
Fix: Grant schema creation permissions. Install lexigram-tenancy[sql]. Switch to row-level isolation as a temporary workaround.

[tenant context not available in SQL queries]

Cause: IntegrationConfig.sql_context_bridge is False, or lexigram-sql is not installed.
Fix: Set sql_context_bridge: true in tenancy config. Verify lexigram-sql is installed.

TenantSlugConflictError: Tenant slug 'acme-corp' already exists

Cause: A tenant with the same slug already exists in the store. Slug must be globally unique.

Fix: Use a unique slug, or append a discriminator:

from lexigram.contracts.tenancy.protocols import TenantProviderProtocol
provider = await container.resolve(TenantProviderProtocol)
result = await provider.create_tenant(name="Acme Corp", slug="acme-corp")
if result.is_err() and isinstance(result.unwrap_err(), TenantSlugConflictError):
result = await provider.create_tenant(name="Acme Corp", slug="acme-corp-2")

Symptom: The subdomain resolver never extracts a tenant ID, even though requests come from acme.app.com.

Cause: ResolutionConfig.subdomain_pattern is None (default). The subdomain resolver requires a base domain pattern to extract the tenant subdomain.

Fix: Set the subdomain pattern in config:

tenancy:
resolution:
subdomain_pattern: "app.com"

With subdomain_pattern: "app.com", a request to acme.app.com extracts acme as the tenant ID.

Tenant provisioning fails on isolation setup

Section titled “Tenant provisioning fails on isolation setup”
TenantProvisioningError: Tenant provisioning failed

Cause: The isolation strategy (row_level or schema) failed during auto_provision_isolation. Common causes: missing database permissions, or lexigram-sql not installed for schema-based isolation.

Fix: Check the provisioning logs and ensure the isolation prerequisites are met:

Terminal window
# For schema isolation, the database user needs CREATE SCHEMA
GRANT CREATE ON DATABASE mydb TO app_user;

Disable auto-provisioning if you handle isolation separately:

tenancy:
lifecycle:
auto_provision_isolation: false

Symptom: TenantConfigProvider.get(tenant_id) returns global config instead of tenant-specific overrides.

Cause: The tenant-specific config was never stored, or the cache TTL hasn’t expired since the overrides were updated.

Fix: Verify overrides were saved:

from lexigram.tenancy.config_overrides import TenantConfigService
store = await container.resolve(TenantConfigService)
await store.set_override(tenant_id, "features.premium", True)
# Clear cache to pick up changes
await store.clear_tenant_cache(tenant_id)

Lower the cache TTL in development:

tenancy:
overrides:
cache_ttl: 5 # seconds (default is higher)