elsa-workflows/elsa-core · error · ProviderHttpException

TransportFailure

TransportFailure

Error message

ProviderHttpException(ProviderHttpFailure.TransportFailure)

What it means

ProviderHttpClientFactory wraps all outbound HTTP calls to the external identity provider (discovery, token, userinfo) and normalizes every failure into a ProviderHttpException with a ProviderHttpFailure code. TransportFailure is the catch-all branch: any exception that is not a cancellation, timeout, or already-classified provider error is rethrown as TransportFailure. It signals a low-level HTTP/network problem (DNS, TLS, connection reset, malformed response stream) rather than an application-level rejection.

Solutions

  1. Verify the provider authority/base URL is correct and reachable from the machine running Elsa (curl the discovery endpoint, e.g. https://<authority>/.well-known/openid-configuration).
  2. Check network egress: firewall rules, proxy env vars (HTTP_PROXY/HTTPS_PROXY), and DNS resolution inside containers; add the CA certificate to the trust store if TLS interception is in place.
  3. Retry the operation — transport failures are often transient; configure HttpClient retry policies (Polly) at the HttpClient level.
  4. Inspect the inner exception/log output for the original Exception details to distinguish DNS vs TLS vs connection-reset causes.

Example fix

// before: app configured with unreachable authority
options.Authority = "https://idp.internal.local";
// after: verify reachability and use correct host/port
options.Authority = "https://idp.internal.local:8443"; // ensure DNS + egress + trust chain allow this
Defensive patterns

Strategy: retry

Validate before calling

// Reachability pre-check before invoking provider calls
using var ping = new HttpClient { Timeout = TimeSpan.FromSeconds(5) };
var resp = await ping.GetAsync(new Uri(new Uri(authority), "/.well-known/openid-configuration"));
if (!resp.IsSuccessStatusCode) throw new InvalidOperationException("Provider authority unreachable");

Try / catch

try
{
    await providerClient.GetAsync(url, ProviderResponseKind.Discovery);
}
catch (ProviderHttpException pex) when (pex.Failure == ProviderHttpFailure.TransportFailure)
{
    logger.LogWarning(pex, "Transport failure contacting provider; will retry");
    // retry with backoff or fail over to alternate authority
}

Prevention

When it happens

Trigger: SendAsync (invoked via GetAsync or PostFormAsync) encounters an unexpected Exception during request transmission or response read while the caller's cancellationToken is NOT cancelled — e.g., HttpClient throws HttpRequestException on connection failure, TLS handshake error, or socket reset.

Common situations: Identity provider host unreachable (wrong authority URL, DNS failure), firewall/proxy blocking egress from a container or Kubernetes pod, TLS certificate issues (self-signed certs, expired CA), transient network blips in clustered deployments, or IPv6/IPv4 resolution problems.

Related errors


AI-assisted analysis of elsa-workflows/elsa-core@fe9217bdfa (2026-09-13). Data as JSON: /api/errors/a349a35687e8a34a. Report an issue: GitHub.

Appendix: source

Thrown at src/modules/Elsa.ExternalAuthentication/Services/ProviderHttpClientFactory.cs:126

                return new(response.StatusCode, await ReadResponseBodyAsync(response, kind, timeout.Token));
            }
        }
        catch (OutboundDestinationException)
        {
            throw new ProviderHttpException(ProviderHttpFailure.DestinationRejected);
        }
        catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
        {
            throw new ProviderHttpException(ProviderHttpFailure.Timeout);
        }
        catch (ProviderHttpException)
        {
            throw;
        }
        catch (Exception) when (!cancellationToken.IsCancellationRequested)
        {
            throw new ProviderHttpException(ProviderHttpFailure.TransportFailure);
        }
    }

    private async Task<byte[]> ReadResponseBodyAsync(HttpResponseMessage response, ProviderResponseKind kind, CancellationToken cancellationToken)
    {
        var limit = GetResponseLimit(kind);
        var contentLength = response.Content.Headers.ContentLength;
        if (contentLength is not null && contentLength > limit)
            throw new ProviderHttpException(ProviderHttpFailure.ResponseTooLarge);

        await using var input = await response.Content.ReadAsStreamAsync(cancellationToken);
        await using var output = new MemoryStream();
        var buffer = new byte[81920];
        while (true)
        {
            var read = await input.ReadAsync(buffer, cancellationToken);
            if (read == 0)
                return output.ToArray();

View on GitHub (pinned to fe9217bdfa)