microsoft/aspire · error · InvalidOperationException

Connection string is unavailable

Error message

Connection string is unavailable

What it means

AddRedis registers a health check whose factory reads the captured connection string; if it is still unavailable when the health check executes, the factory throws InvalidOperationException('Connection string is unavailable'). This surfaces when the health check runs before the ConnectionStringAvailableEvent populated the string, or when connection string resolution failed.

Solutions

  1. Ensure the Redis resource (or its dependency chain) starts and publishes its connection string before health checks run.
  2. Check app host logs for connection string resolution failures or the sibling DistributedApplicationException from the event subscription.
  3. If running in tests, wait for the resource to reach Running/Healthy state before invoking health endpoints.
Defensive patterns

Strategy: retry

Validate before calling

// Caller-side: only query health endpoints after the resource is running.
// await app.ResourceNotifications.WaitForResourceHealthyAsync("redis");

Try / catch

try { await httpClient.GetAsync("/health"); } catch (InvalidOperationException ex) when (ex.Message == "Connection string is unavailable") { await Task.Delay(TimeSpan.FromSeconds(1)); // retry until the connection string is published }

Prevention

When it happens

Trigger: The '{name}_check' Redis health check executes while the closure variable connectionString is null — the health-check callback runs before connection string publication completed.

Common situations: App host startup racing health checks against connection string publication, a hung/failed connection-string resolution (e.g. container not started), or tests hitting health endpoints before the app model is ready.

Related errors


AI-assisted analysis of microsoft/aspire@25830f84bd (2026-09-16). Data as JSON: /api/errors/c2ac574735562487. Report an issue: GitHub.

Appendix: source

Thrown at src/Aspire.Hosting.Redis/RedisBuilderExtensions.cs:93

        // https://github.com/Azure/azure-dev/issues/4848
        var passwordParameter = password?.Resource ?? ParameterResourceBuilderExtensions.CreateDefaultPasswordParameter(builder, $"{name}-password", special: false);

        var redis = new RedisResource(name, passwordParameter);

        string? connectionString = null;

        builder.Eventing.Subscribe<ConnectionStringAvailableEvent>(redis, async (@event, ct) =>
        {
            connectionString = await redis.GetConnectionStringAsync(ct).ConfigureAwait(false);

            if (connectionString == null)
            {
                throw new DistributedApplicationException($"ConnectionStringAvailableEvent was published for the '{redis.Name}' resource but the connection string was null.");
            }
        });

        var healthCheckKey = $"{name}_check";
        builder.Services.AddHealthChecks().AddRedis(sp => connectionString ?? throw new InvalidOperationException("Connection string is unavailable"), name: healthCheckKey);

        var redisBuilder = builder.AddResource(redis)
            .WithEndpoint(port: port, targetPort: 6379, name: RedisResource.PrimaryEndpointName, scheme: RedisResource.StandardRedisScheme)
            .WithImage(RedisContainerImageTags.Image, RedisContainerImageTags.Tag)
            .WithImageRegistry(RedisContainerImageTags.Registry)
            .WithIconName("Database")
            .WithHealthCheck(healthCheckKey)
            // see https://github.com/microsoft/aspire/issues/3838 for why the password is passed this way
            .WithEntrypoint("/bin/sh")
            .WithEnvironment(context =>
            {
                if (redis.PasswordParameter is { } password)
                {
                    context.EnvironmentVariables["REDIS_PASSWORD"] = password;
                }
            })
            .WithArgs(context =>
            {

View on GitHub (pinned to 25830f84bd)