microsoft/aspire · error · InvalidOperationException

Connection string is unavailable

Error message

Connection string is unavailable

What it means

The Redis health check registered by AddGarnet resolves the connection string lazily from a service provider via the captured connectionString variable. If the health check executes before ConnectionStringAvailableEvent populated it (or after the event resolved null), the factory throws InvalidOperationException 'Connection string is unavailable', so the health check reports failure instead of passing null to the Redis client.

Solutions

  1. Let the AppHost start normally — the health check typically recovers once ConnectionStringAvailableEvent resolves the value; verify the resource reaches Running.
  2. If it never resolves, check logs for the related DistributedApplicationException (error 978) indicating the event handler failed.
  3. Wait for the resource to be healthy before depending on it: use WaitFor(garnet) in dependent resources so consumers don't start before the connection string exists.
  4. If seen only in unit tests, ensure the test host runs the resource's eventing/health-check pipeline the same way the real AppHost does.

Example fix

// before
var cache = builder.AddRedis("redis");
var api = builder.AddProject<Projects.Api>("api").WithReference(cache);
// after — dependents wait until Garnet's connection string and health check are ready
var cache = builder.AddGarnet("cache");
var api = builder.AddProject<Projects.Api>("api").WithReference(cache).WaitFor(cache);
Defensive patterns

Strategy: fallback

Try / catch

builder.Services.AddHealthChecks().AddRedis(sp =>
    connectionString ?? throw new InvalidOperationException("Connection string is unavailable"),
    name: healthCheckKey);
// Health checks will surface this as an unhealthy status until the event resolves the value.

Prevention

When it happens

Trigger: Health check for the '{name}_check' registration runs while the local connectionString variable is still null — i.e. health checks probe the Garnet resource before ConnectionStringAvailableEvent has been handled or when the event handler failed to set a value.

Common situations: Dashboard/health probes hitting the resource very early during AppHost startup before the connection string is resolved; a stuck or failed event handler leaving connectionString null; tests creating the resource without the normal eventing pipeline.

Understand the failure class

Background: "missing required config value" errors: why libraries refuse to start when a configuration key is empty, unset, or blank — this error's family across 48 libraries.

Related errors


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

Appendix: source

Thrown at src/Aspire.Hosting.Garnet/GarnetBuilderExtensions.cs:127

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

        var garnet = new GarnetResource(name, passwordParameter);

        string? connectionString = null;

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

            if (connectionString == null)
            {
                throw new DistributedApplicationException($"ConnectionStringAvailableEvent was published for the '{garnet.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);

        return builder.AddResource(garnet)
            .WithEndpoint(port: port, targetPort: 6379, name: GarnetResource.PrimaryEndpointName)
            .WithImage(GarnetContainerImageTags.Image, GarnetContainerImageTags.Tag)
            .WithImageRegistry(GarnetContainerImageTags.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 (garnet.PasswordParameter is { } password)
                {
                    context.EnvironmentVariables["GARNET_PASSWORD"] = password;
                }
            })
            .WithArgs(context =>
            {

View on GitHub (pinned to 25830f84bd)