elsa-workflows/elsa-core · error · InvalidOperationException
Tenant has not been resolved.
Error message
Tenant has not been resolved.
What it means
A sentinel guard on TenantResolverResult: accessing the resolved tenant (the TenantId getter) when the result was produced by the Unresolved factory (IsResolved = false) throws. It signals a programming error — the caller failed to check whether a tenant was resolved before reading the tenant id.
Solutions
- Check IsResolved before calling ResolveTenantId and handle the unresolved case explicitly.
- Configure a tenant resolver (header, cookie, etc.) appropriate for your requests in multi-tenant mode.
- Provide a default/ambient tenant provider for non-HTTP execution contexts.
- Log the resolver pipeline to see why no tenant matched.
Example fix
// before
var tenantId = result.ResolveTenantId();
// after
if (!result.IsResolved) throw new BadRequestException("Tenant header missing");
var tenantId = result.ResolveTenantId(); Defensive patterns
Strategy: type-guard
Validate before calling
if (!result.IsResolved)
throw new UnauthorizedAccessException("No tenant could be resolved from the request."); Type guard
bool TryResolveTenantId(TenantResolverResult result, out string? tenantId) { tenantId = result.IsResolved ? result.TenantId : null; return result.IsResolved; } Try / catch
try { tenantId = result.ResolveTenantId(); }
catch (InvalidOperationException ex) when (ex.Message == "Tenant has not been resolved.") { httpContext.Response.StatusCode = 400; } Prevention
- Configure tenant resolvers before enabling multi-tenancy
- Test requests both with and without tenant headers
- Provide an explicit default tenant for background/non-HTTP work
When it happens
Trigger: Calling ResolveTenantId on a result whose IsResolved is false — i.e. the resolver could not determine a tenant (no tenant resolver matched, no header/cookie/API key identified a tenant).
Common situations: Requesting Elsa APIs without tenant-identifying headers in multi-tenant mode; misconfigured tenant resolvers; background/hosted code executing outside the HTTP pipeline where no resolution ran.
Understand the failure class
Background: "X is required", "must be set", "cannot be empty": the missing-required-config error family, from Vertex AI project/location to WeChat keys — this error's family across 18 libraries.
Related errors
- A conversation ID is required. (Parameter 'conversation')
- A conversation user ID is required. (Parameter…
- AI conversation entity metadata was not found.
- AI conversation table metadata was not found.
- Build() must be called before accessing services
AI-assisted analysis of elsa-workflows/elsa-core@fe9217bdfa (2026-09-13).
Data as JSON: /api/errors/202f39abc6394591.
Report an issue: GitHub.
Appendix: source
Thrown at src/modules/Elsa.Common/Multitenancy/Results/TenantResolverResult.cs:39
/// <summary>
/// Creates a new instance of <see cref="TenantResolverResult"/> representing a resolved tenant.
/// </summary>
/// <param name="tenantId">The resolved tenant.</param>
/// <returns>A new instance of <see cref="TenantResolverResult"/> representing a resolved tenant.</returns>
public static TenantResolverResult Resolved(string? tenantId) => new(tenantId, true);
/// <summary>
/// Creates a new instance of <see cref="TenantResolverResult"/> representing an unresolved tenant.
/// </summary>
/// <returns>A new instance of <see cref="TenantResolverResult"/> representing an unresolved tenant.</returns>
public static TenantResolverResult Unresolved() => new(null, false);
/// <summary>
/// Gets a value indicating whether the tenant has been resolved.
/// </summary>
public bool IsResolved => _isResolved;
public string ResolveTenantId() => TenantId ?? throw new InvalidOperationException("Tenant has not been resolved.");
}View on GitHub (pinned to fe9217bdfa)