microsoft/aspire · error · InvalidOperationException

ClusterIssuer ' ' has no solvers configured. Add at least…

Error message

ClusterIssuer '{issuer.Name}' has no solvers configured. Add at least one solver via WithHttp01Solver().

What it means

After verifying the issuer has a spec, ApplyClusterIssuerAsync also requires at least one challenge solver (e.g. HTTP-01) because cert-manager cannot complete ACME challenges without one. If issuer.Solvers is empty the publisher throws InvalidOperationException.

Solutions

  1. Add .WithHttp01Solver() to the ClusterIssuer builder chain.
  2. If using DNS challenges via a custom solver type, ensure the corresponding solver extension is applied.
  3. Verify no code path clears the Solvers collection after configuration.

Example fix

// before
certManager.AddClusterIssuer("letsencrypt").WithLetsEncryptProduction(email);

// after
certManager.AddClusterIssuer("letsencrypt")
    .WithLetsEncryptProduction(email)
    .WithHttp01Solver();
Defensive patterns

Strategy: validation

Validate before calling

if (issuer.Solvers.Count == 0) issuerBuilder.WithHttp01Solver(); // ensure a solver exists

Prevention

When it happens

Trigger: Publishing/running a model where an issuer was configured with an ACME spec (e.g. WithLetsEncryptProduction) but no solver was added via WithHttp01Solver() (or other solver extension).

Common situations: Developers configure the ACME account/email but forget the challenge solver step, or remove solver configuration during refactoring.

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 microsoft/aspire@25830f84bd (2026-09-16). Data as JSON: /api/errors/8d1ffea9318abde1. Report an issue: GitHub.

Appendix: source

Thrown at src/Aspire.Hosting.Kubernetes/CertManagerExtensions.cs:433

        return Task.FromResult<IEnumerable<PipelineStep>>(steps);
    }

    private static async Task ApplyClusterIssuerAsync(
        PipelineStepContext context,
        CertManagerResource certManager,
        CertManagerIssuerResource issuer)
    {
        if (issuer.Spec is null)
        {
            throw new InvalidOperationException(
                $"ClusterIssuer '{issuer.Name}' has no spec. Configure it with WithLetsEncryptProduction(), " +
                "WithLetsEncryptStaging(), or WithAcmeServer().");
        }

        if (issuer.Solvers.Count == 0)
        {
            throw new InvalidOperationException(
                $"ClusterIssuer '{issuer.Name}' has no solvers configured. Add at least one " +
                "solver via WithHttp01Solver().");
        }

        var environment = certManager.Parent;

        var manifest = await BuildClusterIssuerManifestAsync(context.Model, certManager, issuer, context.Logger, context.CancellationToken)
            .ConfigureAwait(false);

        context.Logger.LogInformation(
            "Applying cert-manager ClusterIssuer '{IssuerName}'.", issuer.Name);

        // Write to a temp file and apply. kubectl apply -f - via stdin would avoid the
        // temp file but ProcessUtil.Run doesn't expose a stdin pipe; Directory.CreateTempSubdirectory
        // is the standard temp pattern in this codebase (see EnsureBootstrapTlsSecretAsync).
        var tempDir = Directory.CreateTempSubdirectory(".aspire-cm-issuer");
        try
        {

View on GitHub (pinned to 25830f84bd)