microsoft/aspire · error · ProjectUpdaterException
Could not find '#:sdk Aspire.AppHost.Sdk@
Error message
Could not find '#:sdk Aspire.AppHost.Sdk@<version>' directive in single-file AppHost: {0} What it means
Single-file AppHosts (.cs) declare their SDK with a file directive `#:sdk Aspire.AppHost.Sdk@<version>`. UpdateSdkVersionInSingleFileAppHostAsync regex-matches that directive; if no line matches, it cannot update the SDK version and throws ProjectUpdaterException.
Solutions
- Add or restore the directive at the top of the .cs AppHost: `#:sdk Aspire.AppHost.Sdk@<current-version>`, then re-run `aspire update`.
- Check spelling/casing: the directive must be exactly `#:sdk Aspire.AppHost.Sdk@` followed by a version.
- Edit the version in the directive manually if you prefer to skip the updater for this file.
Example fix
// before (top of apphost.cs) #:project // after #:sdk Aspire.AppHost.Sdk@9.4.0
Defensive patterns
Strategy: validation
Validate before calling
var content = File.ReadAllText(singleFileAppHost);
if (!System.Text.RegularExpressions.Regex.IsMatch(content, "#:sdk Aspire\.AppHost\.Sdk@[^\s]+"))
throw new InvalidOperationException($"{singleFileAppHost} lacks the #:sdk Aspire.AppHost.Sdk@<version> directive."); Try / catch
try { await updater.UpdateAsync(...); }
catch (ProjectUpdaterException ex) when (ex.Message.Contains("#:sdk Aspire.AppHost.Sdk"))
{
// add the directive at the top of the file, then re-run update
} Prevention
- Keep the #:sdk directive as the first line of single-file AppHosts; never delete it during edits.
- Use exact casing: `#:sdk Aspire.AppHost.Sdk@` followed by a version number.
- Run `aspire update` only on files produced by the Aspire single-file AppHost template.
When it happens
Trigger: Updating a single-file AppHost whose content lacks a `#:sdk Aspire.AppHost.Sdk@<version>` directive — e.g. directive deleted, different SDK name, wrong @ separator, or the .cs file isn't actually a single-file AppHost.
Common situations: Hand-edited single-file AppHost where the directive was removed or reformatted (e.g. `#:sdk aspire.apphost.sdk@9.0.0` casing mismatch); an older single-file layout predating the directive convention; a plain .cs file misidentified as an AppHost.
Understand the failure class
Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.
Related errors
- Could not find root element in
- Could not find <Sdk Name='Aspire.AppHost.Sdk' /> element in
- Unsupported AppHost file type
- -32603
- ASPIRE_TERMINAL_HOST_INVOCATION_ARGS has unterminated
AI-assisted analysis of microsoft/aspire@25830f84bd (2026-09-16).
Data as JSON: /api/errors/d6f7218bf3f407e8.
Report an issue: GitHub.
Appendix: source
Thrown at src/Aspire.Cli/Projects/ProjectUpdater.cs:881
"removed from '{1}': {2}. Please manually remove the <PackageVersion Include=\"{0}\" ... /> " +
"entry from this file to avoid NU1009 build errors.",
packageId,
cpmInfo.DirectoryPackagesPropsFile.FullName,
ex.Message),
ex);
}
}
private static async Task UpdateSdkVersionInSingleFileAppHostAsync(FileInfo projectFile, NuGetPackageCli package)
{
var fileContent = await File.ReadAllTextAsync(projectFile.FullName);
// Look for the #:sdk Aspire.AppHost.Sdk@<version> directive
var match = SdkDirectiveRegex().Match(fileContent);
if (!match.Success)
{
throw new ProjectUpdaterException(string.Format(CultureInfo.InvariantCulture,
"Could not find '#:sdk Aspire.AppHost.Sdk@<version>' directive in single-file AppHost: {0}", projectFile.FullName));
}
// Replace the matched SDK directive with the new version
var newDirective = $"#:sdk Aspire.AppHost.Sdk@{package.Version}";
var updatedContent = SdkDirectiveRegex().Replace(fileContent, newDirective, 1);
// The new SDK pulls Aspire.Hosting.AppHost in implicitly, so a leftover
// explicit `#:package Aspire.Hosting.AppHost@<version>` directive in the
// same file is redundant. Strip it here so the SDK-bump path matches
// the csproj migration in UpdateSdkVersionInProjectAppHostAsync. The
// SDK-already-current path goes through EnqueueLegacyAppHostCleanupStepIfNeeded.
updatedContent = LegacyAppHostPackageDirectiveRegex().Replace(updatedContent, string.Empty);
await File.WriteAllTextAsync(projectFile.FullName, updatedContent);
}
[GeneratedRegex(@"#:sdk\s+Aspire\.AppHost\.Sdk@(?:[\d\.\-a-zA-Z]+|\*)")]View on GitHub (pinned to 25830f84bd)