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

  1. Check IsResolved before calling ResolveTenantId and handle the unresolved case explicitly.
  2. Configure a tenant resolver (header, cookie, etc.) appropriate for your requests in multi-tenant mode.
  3. Provide a default/ambient tenant provider for non-HTTP execution contexts.
  4. 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

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


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)