microsoft/aspire · error · InvalidOperationException

Could not replace Aspire skills cache directory

Error message

Could not replace Aspire skills cache directory '{0}'.

What it means

AspireSkillsInstaller attempts an atomic replacement of the Aspire skills cache directory by deleting the existing target and moving a staged directory into place. This error is thrown when the old target directory still exists after a delete was attempted, because the MoveTo would otherwise fail or merge incorrectly. It almost always indicates an external process (or antivirus/indexer) is holding a lock on files inside the directory, or the user lacks delete permission.

Solutions

  1. Close other aspire/CLI processes and editors that may hold handles under the skills cache directory, then retry the command.
  2. Manually delete the cache directory reported in the message (checking for read-only files) and re-run the install.
  3. Temporarily exclude the cache directory from antivirus/indexing if locks recur.
  4. Re-run the command in an elevated shell if the cache is under a protected path (e.g. Program Files).
  5. Retry after a short delay — transient Windows locks (e.g. from AV scans) often clear on their own.

Example fix

// before
shell: rm -rf partially-locked-cache && aspire skills install
// after
# close other CLI sessions first, then
rm -rf ~/.aspire/skills/cache/<version>
aspire skills install
Defensive patterns

Strategy: retry

Try / catch

try
{
    await installer.InstallAsync(...);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("Could not replace Aspire skills cache directory"))
{
    // wait for locks to clear, close other processes, then retry once
}

Prevention

When it happens

Trigger: Occurs during skill cache install/refresh when Directory.Exists(targetDir) is still true after TryDeleteDirectory(targetDir). Caused by open file handles in the cache dir, read-only attributes, antivirus scans, or insufficient ACLs on the cache path.

Common situations: Another aspire CLI process or editor is concurrently reading the skills cache; Windows Defender or a search indexer temporarily locks files; the cache lives on a network share or synced folder (OneDrive/Dropbox) with lock/pin semantics; running under a restricted service account.

Understand the failure class

Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.

Related errors


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

Appendix: source

Thrown at src/Aspire.Cli/Agents/AspireSkills/AspireSkillsInstaller.cs:811

            version,
            archiveSha512,
            githubArchiveSha256,
            requireVerifiedGitHubSource: source == BundleArchiveSource.VerifiedGitHub,
            skipCompatibilityCheck: source == BundleArchiveSource.Embedded,
            activity: null,
            cancellationToken).ConfigureAwait(false);
        if (cachedResult is not null)
        {
            return cachedResult.Bundle!;
        }

        if (Directory.Exists(targetDir))
        {
            logger.LogDebug("Replacing Aspire skills cache directory {CacheDirectory}.", targetDir);
            TryDeleteDirectory(targetDir);
            if (Directory.Exists(targetDir))
            {
                throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Could not replace Aspire skills cache directory '{0}'.", targetDir));
            }
        }

        Directory.CreateDirectory(versionCacheDirectory);
        RemoveLegacyCacheLayout(versionCacheDirectory);
        stageDirectory.MoveTo(targetDir);
        TouchLastUsed(targetDir);

        return stagedBundle;
    }

    private static void RemoveInstallerMetadata(string bundleDirectory)
    {
        // These files describe local installer state, not bundle content. Always recreate them
        // from the acquisition path so an archive cannot claim freshness or GitHub provenance.
        File.Delete(Path.Combine(bundleDirectory, ArchiveSha512FileName));
        File.Delete(Path.Combine(bundleDirectory, GitHubArchiveSha256FileName));
        File.Delete(Path.Combine(bundleDirectory, GitHubAttestationVerifiedFileName));

View on GitHub (pinned to 25830f84bd)