microsoft/aspire · error · InvalidOperationException
Downloaded Aspire skills package does not contain…
Error message
Downloaded Aspire skills package does not contain skill-manifest.json.
What it means
AspireSkillsBundleProvider locates the root directory of a downloaded skills bundle by searching for skill-manifest.json. When no directory in the extracted archive contains that manifest, FindBundleRoot throws this InvalidOperationException, meaning the archive is not a valid Aspire skills bundle layout.
Solutions
- Verify the release asset zip actually contains skill-manifest.json at its root or directly inside a single top-level directory
- Re-download the bundle; a truncated download may have failed to extract the manifest
- Regenerate and publish the skills bundle with the manifest included
Defensive patterns
Strategy: validation
Validate before calling
// before installing, inspect the extracted bundle
var manifests = Directory.EnumerateFiles(extractDir, "skill-manifest.json", SearchOption.AllDirectories).ToList();
if (manifests.Count == 0)
throw new InvalidOperationException($"Bundle at {extractDir} has no skill-manifest.json — not a valid skills bundle."); Try / catch
try
{
await provider.CreateBundleAsync(archivePath);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("does not contain skill-manifest.json"))
{
logger.LogError(ex, "Downloaded skills bundle is missing skill-manifest.json; re-download or use a different release.");
} Prevention
- Always publish release archives that contain skill-manifest.json at the root or one directory deep
- Verify archive layout with CI checks before publishing a skills release
- Use the official bundle tooling rather than hand-zipping directories
When it happens
Trigger: Downloaded/extracted GitHub release asset whose top-level directories (or the archive root) contain no skill-manifest.json; FindBundleRoot is called from bundleRoot during bundle creation.
Common situations: Publishing a release with the wrong artifact (zip without the manifest), mis-zipped archive that nests the manifest one level deeper than expected, corrupted or partially extracted download, or someone packaging skills manually and forgetting skill-manifest.json.
Understand the failure class
Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.
Related errors
- Aspire skills bundle contains an empty version range.
- Aspire skills bundle contains an invalid version comparator
- Aspire skills bundle contains an invalid version value
- Aspire skills bundle manifest must specify supported Aspire…
- Aspire skills bundle manifest must specify…
AI-assisted analysis of microsoft/aspire@25830f84bd (2026-09-16).
Data as JSON: /api/errors/28b74ecb5afc8187.
Report an issue: GitHub.
Appendix: source
Thrown at src/Aspire.Cli/Agents/AspireSkills/AspireSkillsBundleProvider.cs:605
return new DirectoryInfo(packageDirectory);
}
var topLevelBundleDirectories = Directory
.EnumerateDirectories(extractionDirectory)
.Where(directory => File.Exists(Path.Combine(directory, ManifestFileName)))
.ToArray();
if (topLevelBundleDirectories.Length == 1)
{
return new DirectoryInfo(topLevelBundleDirectories[0]);
}
if (topLevelBundleDirectories.Length > 1)
{
throw new InvalidOperationException("Downloaded Aspire skills package contains multiple skill-manifest.json files.");
}
throw new InvalidOperationException("Downloaded Aspire skills package does not contain skill-manifest.json.");
}
private static void CopyDirectory(string sourceDirectory, string targetDirectory)
{
Directory.CreateDirectory(targetDirectory);
foreach (var sourceFile in Directory.GetFiles(sourceDirectory, "*", SearchOption.AllDirectories))
{
var relativePath = Path.GetRelativePath(sourceDirectory, sourceFile);
var targetFile = Path.Combine(targetDirectory, relativePath);
var targetFileDirectory = Path.GetDirectoryName(targetFile);
if (!string.IsNullOrEmpty(targetFileDirectory))
{
Directory.CreateDirectory(targetFileDirectory);
}
File.Copy(sourceFile, targetFile, overwrite: true);
}View on GitHub (pinned to 25830f84bd)