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 packages

View on GitHub (pinned to 524b4487d9)

Solutions

  1. Open the csproj at the printed path and re-add the closing `</Project>` tag, then retry
  2. Or regenerate: delete the directory, re-run `spacetime init`, and reapply your changes
  3. 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

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


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