clockworklabs/SpacetimeDB · error · anyhow::Error
Invalid .csproj file: missing </Project> tag
Error message
Invalid .csproj file: missing </Project> tag
What it means
Injecting the NativeAOT ItemGroup splits the csproj at the last `</Project>` tag so the new ItemGroup can be inserted before it. A csproj without that closing tag is malformed XML, and the CLI refuses to write AOT configuration into it (init.rs:1933).
Source
Thrown at crates/cli/src/subcommands/init.rs:1933
if !csproj_path.exists() {
anyhow::bail!("Could not find StdbModule.csproj at {}", csproj_path.display());
}
let content = std::fs::read_to_string(&csproj_path)?;
let new_content = if dotnet_major == Some(8) {
// .NET 8 AOT: keep net8.0 TFM, add explicit ILCompiler.LLVM package references.
let native_aot_config = r#"
<ItemGroup Condition="'$(EXPERIMENTAL_WASM_AOT)' == '1'">
<PackageReference Include="Microsoft.DotNet.ILCompiler.LLVM" Version="8.0.0-*" />
<PackageReference Include="runtime.$(NETCoreSdkPortableRuntimeIdentifier).Microsoft.DotNet.ILCompiler.LLVM" Version="8.0.0-*" />
</ItemGroup>
"#;
if let Some(pos) = content.rfind("</Project>") {
let (before, after) = content.split_at(pos);
format!("{}{}{}", before.trim_end(), native_aot_config, after)
} else {
anyhow::bail!("Invalid .csproj file: missing </Project> tag");
}
} else {
// .NET 10 AOT: directly set TFM to net10.0 (no conditional needed).
// ILCompiler.LLVM comes transitively via the SpacetimeDB.Runtime NuGet package.
content.replace(
"<TargetFramework>net8.0</TargetFramework>",
"<TargetFramework>net10.0</TargetFramework>",
)
};
std::fs::write(&csproj_path, new_content)?;
println!(
"{} Added NativeAOT-LLVM project configuration to {}",
"✓".green(),
csproj_path.display()
);
// Create NuGet.Config with the dotnet-experimental feed required for ILCompiler.LLVM packagesView on GitHub (pinned to 524b4487d9)
Solutions
- Open the csproj at the printed path and re-add the closing `</Project>` tag, then retry
- Or regenerate: delete the directory, re-run `spacetime init`, and reapply your changes
- Validate the XML (`xmllint --noout StdbModule.csproj` or `dotnet build`) before retrying publish
Example fix
// before (StdbModule.csproj, truncated) </ItemGroup> // after </ItemGroup> </Project>
Defensive patterns
Strategy: validation
Validate before calling
xmllint --noout "$PROJECT_DIR/StdbModule.csproj" || { echo 'csproj is not well-formed XML' >&2; exit 2; } Type guard
fn has_project_close_tag(csproj: &str) -> bool {
csproj.rfind("</Project>").is_some()
} Prevention
- Treat the csproj as generated: change the TFM via --dotnet-version, not edits
- Resolve merge conflicts in StdbModule.csproj before publishing
- Validate the XML after any tool touches the csproj
When it happens
Trigger: Building/publishing with --native-aot when StdbModule.csproj was truncated, badly merged, or hand-edited so the `</Project>` closing tag disappeared.
Common situations: Unresolved git merge conflicts inside the csproj; codegen or editors mangling the file; partial writes from a crashed process.
Related errors
- Could not find StdbModule.csproj at {}
- --native-aot is only supported for C# projects (--lang cshar
- Invalid C# template: missing net8.0/net10.0 TargetFrameworks
AI-assisted analysis of clockworklabs/SpacetimeDB@524b4487d9 (2026-08-16).
Data as JSON: /api/errors/0285ce6f4f472c72.
Report an issue: GitHub.