clockworklabs/SpacetimeDB · error · anyhow::Error

Invalid C# template: missing net8.0/net10.0 TargetFrameworks

Error message

Invalid C# template: missing net8.0/net10.0 TargetFrameworks property

What it means

When configuring a C# project for a chosen .NET version, the CLI rewrites the template marker `<TargetFrameworks>net8.0;net10.0</TargetFrameworks>` (dual TFM) into a single `<TargetFramework>net8.0</TargetFramework>` or `net10.0`. If that literal marker is absent from StdbModule.csproj, the template is considered invalid and init aborts (init.rs:1887). This is an internal invariant over the embedded template, not user input.

Source

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

        _ => unreachable!("unsupported .NET version should have been validated before init"),
    }
}

fn csharp_csproj_for_target(content: &str, dotnet_major: u8) -> anyhow::Result<String> {
    let target_framework = match dotnet_major {
        8 => "net8.0",
        10 => "net10.0",
        _ => unreachable!("unsupported .NET version should have been validated before init"),
    };
    let target_framework_property = format!("<TargetFramework>{target_framework}</TargetFramework>");
    if content.contains("<TargetFrameworks>net8.0;net10.0</TargetFrameworks>") {
        return Ok(content.replace(
            "<TargetFrameworks>net8.0;net10.0</TargetFrameworks>",
            &target_framework_property,
        ));
    }

    anyhow::bail!("Invalid C# template: missing net8.0/net10.0 TargetFrameworks property")
}

fn configure_csharp_project_files(project_path: &Path, dotnet_major: u8) -> anyhow::Result<()> {
    let global_json_path = project_path.join("global.json");
    std::fs::write(&global_json_path, csharp_global_json(dotnet_major))?;

    let csproj_path = project_path.join("StdbModule.csproj");
    let csproj = std::fs::read_to_string(&csproj_path)?;
    std::fs::write(&csproj_path, csharp_csproj_for_target(&csproj, dotnet_major)?)?;

    Ok(())
}

/// Adds NativeAOT-LLVM project configuration to an existing C# .csproj file and creates NuGet.Config.
///
/// The configuration differs depending on the target .NET version:
///
/// **.NET 8 AOT** (`--native-aot`): Keeps `net8.0` TFM and adds explicit ILCompiler.LLVM 8.0.0-*

View on GitHub (pinned to 524b4487d9)

Solutions

  1. Delete the generated project directory and re-run `spacetime init` from scratch
  2. Or restore the dual-TFM marker `<TargetFrameworks>net8.0;net10.0</TargetFrameworks>` in StdbModule.csproj and retry
  3. Update (or pin) the spacetimedb CLI so the embedded template matches the version that generated the project
  4. Going forward, change the TFM only via --dotnet-version, never by editing the csproj

Example fix

// before (StdbModule.csproj, hand-edited)
<TargetFrameworks>net9.0</TargetFrameworks>
// after (restore the marker the CLI rewrites)
<TargetFrameworks>net8.0;net10.0</TargetFrameworks>
Defensive patterns

Strategy: type-guard

Validate before calling

let csproj = std::fs::read_to_string(project.join("StdbModule.csproj"))?;
if !has_dual_tfm_marker(&csproj) {
    anyhow::bail!("csproj lacks the net8.0/net10.0 marker; regenerate with spacetime init");
}

Type guard

fn has_dual_tfm_marker(csproj: &str) -> bool {
    csproj.contains("<TargetFrameworks>net8.0;net10.0</TargetFrameworks>")
}

Prevention

When it happens

Trigger: Re-running init/build in a directory whose StdbModule.csproj was hand-edited (e.g. TargetFrameworks changed or already collapsed to one TFM), or a CLI version whose embedded template format drifted from what this code expects.

Common situations: Manually editing TargetFrameworks to pin a TFM instead of using --dotnet-version; upgrading the spacetimedb CLI across a template-format change; partially overwritten or merge-conflicted project files.

Related errors


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