{"record":{"id":"36570f756bd5ed6f","repo":"microsoft/aspire","slug":"toolbox-name-changed-concurrently-target-version-version-no","errorCode":null,"errorMessage":"Toolbox '{name}' changed concurrently: target version '{version}' no longer has the expected Aspire ownership and configuration. No further changes were made.","messagePattern":"Toolbox '(.+?)' changed concurrently: target version '(.+?)' no longer has the expected Aspire ownership and configuration\\. No further changes were made\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Aspire.Hosting.Foundry/Toolbox/FoundryToolboxReconciler.cs","lineNumber":503,"sourceCode":"        FoundryToolboxState state,\n        string version)\n    {\n        var target = state.Versions.FirstOrDefault(candidate =>\n            string.Equals(candidate.Version, version, StringComparison.Ordinal));\n        if (target is null)\n        {\n            throw CreateConcurrentChangeException(\n                definition.Name,\n                $\"target version '{version}' is no longer visible\");\n        }\n\n        if (!IsAspireManaged(target) ||\n            !target.Metadata.TryGetValue(\n                FoundryToolboxDeploymentDefinition.ConfigurationHashMetadataKey,\n                out var configurationHash) ||\n            !string.Equals(configurationHash, definition.ConfigurationHash, StringComparison.Ordinal))\n        {\n            throw CreateConcurrentChangeException(\n                definition.Name,\n                $\"target version '{version}' no longer has the expected Aspire ownership and configuration\");\n        }\n    }\n\n    private static bool IsAspireManaged(FoundryToolboxVersionState version) =>\n        version.Metadata.TryGetValue(\n            FoundryToolboxDeploymentDefinition.ManagedByMetadataKey,\n            out var managedBy) &&\n        string.Equals(\n            managedBy,\n            FoundryToolboxDeploymentDefinition.ManagedByMetadataValue,\n            StringComparison.Ordinal);\n\n    private static InvalidOperationException CreateConcurrentChangeException(\n        string name,\n        string detail) =>\n        new(","sourceCodeStart":485,"sourceCodeEnd":521,"githubUrl":"https://github.com/microsoft/aspire/blob/25830f84bd145686607ad00c057b3f84e2e51d43/src/Aspire.Hosting.Foundry/Toolbox/FoundryToolboxReconciler.cs#L485-L521","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Re-run the deploy so Aspire republishes the version with its own ownership metadata and configuration hash, then promotes it","Identify and stop the external edit (portal change, script) that mutated the version's configuration or stripped metadata","If the configuration change was intentional, update the AppHost definition so the configuration hash matches the desired state and redeploy","Delete the drifted version (via Aspire or portal) and redeploy to recreate it cleanly"],"exampleFix":"// before: portal edit changed the version's settings after Aspire published it\n\n// after: change the configuration in the AppHost instead, so the hash matches\ncbuilder.AddFoundryToolbox(\"my-toolbox\", version: \"1.3.0\", configure: t => t.WithSetting(\"maxInstances\", 5));","handlingStrategy":"validation","validationCode":"// Compare the deployed version's config hash with the desired definition before promoting\nvar state = await administration.GetAsync(definition.Name, ct);\nvar target = state?.Versions.FirstOrDefault(v => v.Version == definition.Version);\nbool hashMatches = target?.Metadata.TryGetValue(\"aspire.configurationHash\", out var hash) == true\n    && string.Equals(hash, definition.ConfigurationHash, StringComparison.Ordinal);\nif (target is not null && !hashMatches) { /* drift detected; redeploy to republish */ }","typeGuard":"bool HasExpectedHash(FoundryToolboxVersionState v, string expected) =>\n    v.Metadata.TryGetValue(\"aspire.configurationHash\", out var h) &&\n    string.Equals(h, expected, StringComparison.Ordinal);","tryCatchPattern":"try\n{\n    await reconciler.ReconcileAsync(definition, ct);\n}\ncatch (ConcurrentChangeException)\n{\n    // Version config was mutated externally; redeploying republishes it with correct metadata.\n    await reconciler.ReconcileAsync(definition, ct);\n}","preventionTips":["Change Toolbox configuration only through the AppHost definition, never by direct portal edits","Redeploy after any intentional configuration change so the stored hash matches","Alert on metadata/ownership changes to Aspire-managed versions"],"tags":["concurrency","azure","configuration-drift","reconciliation"],"backgroundTag":"concurrent-change-detected","analyzedSha":"25830f84bd145686607ad00c057b3f84e2e51d43","analyzedAt":"2026-09-16T11:10:06.193Z","contentChangedAt":"2026-09-16T11:10:06.193Z","schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}