microsoft/aspire · error · InvalidOperationException

Toolbox ' ' changed concurrently: expected , but version '…

Error message

Toolbox '{name}' changed concurrently: expected {expectedDefault}, but version '{before.DefaultVersion}' is now the default. No further changes were made.

What it means

The reconciler records the Toolbox's default version observed during reconciliation and re-checks it right before promoting the owned version, because Foundry Toolbox administration lacks conditional updates. This error means the default changed between the check and the promotion (or a default exists where none was observed), so the reconciler aborts without changes to avoid overwriting another deployment's newer default.

Solutions

  1. Re-run the deployment so it observes the current default and re-promotes deterministically.
  2. Serialize deployments against the same Foundry project (CI concurrency groups, locks).
  3. Use separate Toolbox names or Foundry projects per environment/branch.
  4. Verify which deployment changed the default via audit logs and align configurations.
Defensive patterns

Strategy: retry

Try / catch

try
{
    await deploy.RunAsync();
}
catch (Exception ex) when (ex.Message.Contains("is now the default"))
{
    logger.LogWarning(ex, "Toolbox default changed concurrently; retrying deployment.");
    await deploy.RunAsync();
}

Prevention

When it happens

Trigger: PromoteOwnedVersionAsync reads 'before', then finds before.DefaultVersion does not equal observedDefaultVersion (ordinal), or observedDefaultVersion is null while a default now exists; ReconcileAsync throws the concurrent-change error.

Common situations: Two Aspire deployments promoting different versions to the same Toolbox; manual default promotion in the Azure portal during a deploy; a retry of an old deployment racing a newer successful one.

Related errors


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

Appendix: source

Thrown at src/Aspire.Hosting.Foundry/Toolbox/FoundryToolboxReconciler.cs:437

        var before = await administration.GetAsync(definition.Name, cancellationToken).ConfigureAwait(false)
            ?? throw CreateConcurrentChangeException(
                definition.Name,
                $"version '{version}' was created or selected, but the Toolbox is no longer visible");

        ValidateOwnedDefault(definition.Name, before, concurrentChange: true);
        ValidatePromotionTarget(definition, before, version);
        if (string.Equals(before.DefaultVersion, version, StringComparison.Ordinal))
        {
            return;
        }

        if (observedDefaultVersion is null ||
            !string.Equals(before.DefaultVersion, observedDefaultVersion, StringComparison.Ordinal))
        {
            var expectedDefault = observedDefaultVersion is null
                ? "no default version"
                : $"default version '{observedDefaultVersion}'";
            throw CreateConcurrentChangeException(
                definition.Name,
                $"expected {expectedDefault}, but version '{before.DefaultVersion}' is now the default");
        }

        await administration.PromoteVersionAsync(
            definition.Name,
            version,
            cancellationToken).ConfigureAwait(false);

        var after = await administration.GetAsync(definition.Name, cancellationToken).ConfigureAwait(false)
            ?? throw CreateConcurrentChangeException(
                definition.Name,
                "the Toolbox was no longer visible after promotion");

        ValidateOwnedDefault(definition.Name, after, concurrentChange: true);
        ValidatePromotionTarget(definition, after, version);
        if (!string.Equals(after.DefaultVersion, version, StringComparison.Ordinal))
        {

View on GitHub (pinned to 25830f84bd)