microsoft/aspire · error · InvalidOperationException

Toolbox ' ' changed concurrently: target version ' ' no…

Error message

Toolbox '{name}' changed concurrently: target version '{version}' no longer has the expected Aspire ownership and configuration. No further changes were made.

What it means

ValidatePromotionTarget verifies the promoted version is still Aspire-managed and that its stored configuration hash still matches the definition's ConfigurationHash. If the version's metadata was stripped (ownership removed) or its configuration was modified by another actor so the hash no longer matches, the reconciler throws this error and stops. It guarantees the version being promoted is exactly what Aspire published, not an externally mutated one.

Solutions

  1. Re-run the deploy so Aspire republishes the version with its own ownership metadata and configuration hash, then promotes it
  2. Identify and stop the external edit (portal change, script) that mutated the version's configuration or stripped metadata
  3. If the configuration change was intentional, update the AppHost definition so the configuration hash matches the desired state and redeploy
  4. Delete the drifted version (via Aspire or portal) and redeploy to recreate it cleanly

Example fix

// before: portal edit changed the version's settings after Aspire published it

// after: change the configuration in the AppHost instead, so the hash matches
cbuilder.AddFoundryToolbox("my-toolbox", version: "1.3.0", configure: t => t.WithSetting("maxInstances", 5));
Defensive patterns

Strategy: validation

Validate before calling

// Compare the deployed version's config hash with the desired definition before promoting
var state = await administration.GetAsync(definition.Name, ct);
var target = state?.Versions.FirstOrDefault(v => v.Version == definition.Version);
bool hashMatches = target?.Metadata.TryGetValue("aspire.configurationHash", out var hash) == true
    && string.Equals(hash, definition.ConfigurationHash, StringComparison.Ordinal);
if (target is not null && !hashMatches) { /* drift detected; redeploy to republish */ }

Type guard

bool HasExpectedHash(FoundryToolboxVersionState v, string expected) =>
    v.Metadata.TryGetValue("aspire.configurationHash", out var h) &&
    string.Equals(h, expected, StringComparison.Ordinal);

Try / catch

try
{
    await reconciler.ReconcileAsync(definition, ct);
}
catch (ConcurrentChangeException)
{
    // Version config was mutated externally; redeploying republishes it with correct metadata.
    await reconciler.ReconcileAsync(definition, ct);
}

Prevention

When it happens

Trigger: PromoteOwnedVersionAsync reads the post-promotion Toolbox state and the target version fails IsAspireManaged, lacks FoundryToolboxDeploymentDefinition.ConfigurationHashMetadataKey metadata, or has a configuration hash differing from definition.ConfigurationHash.

Common situations: Someone edits the version's configuration in the Foundry portal between publish and promote; an external tool overwrites version settings or metadata; a hand-rolled script imports a same-named version without Aspire's metadata; partial manual rollback strips ownership tags.

Related errors


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

Appendix: source

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

        FoundryToolboxState state,
        string version)
    {
        var target = state.Versions.FirstOrDefault(candidate =>
            string.Equals(candidate.Version, version, StringComparison.Ordinal));
        if (target is null)
        {
            throw CreateConcurrentChangeException(
                definition.Name,
                $"target version '{version}' is no longer visible");
        }

        if (!IsAspireManaged(target) ||
            !target.Metadata.TryGetValue(
                FoundryToolboxDeploymentDefinition.ConfigurationHashMetadataKey,
                out var configurationHash) ||
            !string.Equals(configurationHash, definition.ConfigurationHash, StringComparison.Ordinal))
        {
            throw CreateConcurrentChangeException(
                definition.Name,
                $"target version '{version}' no longer has the expected Aspire ownership and configuration");
        }
    }

    private static bool IsAspireManaged(FoundryToolboxVersionState version) =>
        version.Metadata.TryGetValue(
            FoundryToolboxDeploymentDefinition.ManagedByMetadataKey,
            out var managedBy) &&
        string.Equals(
            managedBy,
            FoundryToolboxDeploymentDefinition.ManagedByMetadataValue,
            StringComparison.Ordinal);

    private static InvalidOperationException CreateConcurrentChangeException(
        string name,
        string detail) =>
        new(

View on GitHub (pinned to 25830f84bd)