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
- Re-run the deployment so it observes the current default and re-promotes deterministically.
- Serialize deployments against the same Foundry project (CI concurrency groups, locks).
- Use separate Toolbox names or Foundry projects per environment/branch.
- 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
- Serialize deployments (locks/concurrency groups) so promotions don't race.
- Use separate Toolbox names or Foundry projects per environment.
- Re-run stale deployments rather than layering them over newer successful ones.
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
- Toolbox ' ' changed concurrently: default version ' '…
- Toolbox ' ' changed concurrently: default version ' '…
- Toolbox ' ' changed concurrently: version ' ' was created…
- Project ' ' does not have a valid connection string.
- Project ' ' does not have a valid endpoint.
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)