microsoft/aspire · error · InvalidOperationException

Toolbox ' ' changed concurrently: version ' ' was promoted…

Error message

Toolbox '{name}' changed concurrently: version '{version}' was promoted, but version '{after.DefaultVersion}' is now the default. No further changes were made.

What it means

After promoting a Toolbox version, PromoteOwnedVersionAsync verifies that the promoted version actually became the default by re-reading state and comparing after.DefaultVersion to the promoted version. If someone changed the default version concurrently (promoted a different version or reassigned the default), the reconciler throws this error instead of silently accepting a default it did not intend. This keeps Aspire's declared model authoritative over the deployed default.

Solutions

  1. Re-run the deploy so Aspire re-promotes the intended version and the default converges
  2. Find and stop the concurrent actor changing the default (portal action, second pipeline), then re-run
  3. Check the Toolbox's default version in the portal and align your AppHost's declared version with whoever should own it
  4. Serialize deployments per environment (pipeline concurrency group) to prevent competing promotions

Example fix

// before: concurrent deploys promote different versions
// pipeline A: version 1.2.0, pipeline B: version 1.3.0

// after: single serialized pipeline promoting the intended version
cbuilder.AddFoundryToolbox("my-toolbox", version: "1.3.0");
Defensive patterns

Strategy: retry

Validate before calling

// Before deploying, check the current default matches expectations
var state = await administration.GetAsync(definition.Name, ct);
if (state?.DefaultVersion is not null && state.DefaultVersion != definition.Version)
    logger.LogWarning("Default version drift detected: {Default}", state.DefaultVersion);

Try / catch

try
{
    await reconciler.ReconcileAsync(definition, ct);
}
catch (ConcurrentChangeException)
{
    // Someone changed the default concurrently; re-running converges the default.
    await reconciler.ReconcileAsync(definition, ct);
}

Prevention

When it happens

Trigger: ReconcileAsync promotes definition version X, then the verification GetAsync shows a different DefaultVersion — caused by another actor promoting/setting a different default between the PromoteVersionAsync call and the verification read.

Common situations: Two developers or pipelines deploying different versions of the same Toolbox at once; a manual 'set as default' action in the Foundry portal during a deploy; a scheduled job pinning an older default while CI promotes a newer one.

Related errors


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

Appendix: source

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

                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))
        {
            throw CreateConcurrentChangeException(
                definition.Name,
                $"version '{version}' was promoted, but version '{after.DefaultVersion}' is now the default");
        }
    }

    private static void ValidateOwnedDefault(
        string name,
        FoundryToolboxState state,
        bool concurrentChange = false)
    {
        if (IsAspireManaged(state.Default))
        {
            return;
        }

        if (concurrentChange)
        {
            throw CreateConcurrentChangeException(

View on GitHub (pinned to 25830f84bd)