clockworklabs/SpacetimeDB · error · anyhow::Error

Invalid .csproj file: missing </Project> tag

Error message

Invalid .csproj file: missing </Project> tag

What it means

On the .NET 8 AOT path, init appends ILCompiler.LLVM package references by locating the last `</Project>` closing tag and splicing before it. A csproj without that tag (truncated, hand-edited into invalid XML, or a forked template that dropped it) cannot be edited, so init aborts.

Solutions

  1. Open the csproj and fix the XML so it ends with a proper `</Project>`; validate with `dotnet build`
  2. Regenerate the C# project and re-apply custom edits afterwards
  3. Forked templates: keep a well-formed csproj with the closing tag
  4. Consider `--dotnet-version 10` AOT, which only string-replaces the TFM and is more tolerant

Example fix

<!-- before: truncated csproj -->
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
<!-- after -->
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
  </PropertyGroup>
</Project>
Defensive patterns

Strategy: validation

Validate before calling

# quick XML sanity check before init's AOT step
grep -q '</Project>' spacetimedb/StdbModule.csproj && echo ok || echo 'csproj not closed'

Prevention

When it happens

Trigger: --native-aot with --dotnet-version 8 against a malformed or truncated csproj; forked/custom template emitting nonstandard XML; file corrupted mid-write.

Common situations: Hand-edited csproj with mismatched tags; template forks; editors or lint tools mangling the file.

Related errors


AI-assisted analysis of clockworklabs/SpacetimeDB@fdd647dfac (2026-08-30). Data as JSON: /api/errors/0285ce6f4f472c72. Report an issue: GitHub.

Appendix: source

Thrown at crates/cli/src/subcommands/init.rs:1970

                    "runtime.$(NETCoreSdkPortableRuntimeIdentifier).Microsoft.DotNet.ILCompiler.LLVM",
                    "8.0.0-*",
                );

        if has_net8_native_aot_refs {
            content
        } else {
            // .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()
    );

View on GitHub (pinned to fdd647dfac)