dotnet/runtime · critical · Error

Mismatched git hashes between loader and runtime. Loader

Error message

Mismatched git hashes between loader and runtime. Loader: ${runtimeApi.runtimeBuildInfo.gitHash}, DOTNET_INTEROP: ${_ems_.DOTNET_INTEROP.gitHash}

What it means

dotnetInitializeModule throws this when the loader's runtimeApi.runtimeBuildInfo.gitHash is non-empty AND differs from the git hash stamped into the runtime's DOTNET_INTEROP.gitHash. It is a build-consistency guard: the JS loader (dotnet.runtime.js / dotnet.native.js glue) and the managed runtime DLLs must come from the same build; otherwise marshaler tables, ABI offsets, and export names can mismatch and corrupt interop.

Solutions

  1. Republish and redeploy all runtime artifacts together from a single `dotnet publish` (or matching SDK build) so loader and runtime share a git hash.
  2. Clear the browser/CDN cache of dotnet.runtime.js, dotnet.native.js, dotnet.native.wasm, and the managed DLLs, then reload.
  3. Pin your CDN/bundle versions explicitly (e.g. https://my.cdn/dotnet/8.0.7/...) so JS and wasm never drift.
  4. Verify by comparing the gitHash in the published dotnet.runtime.js header with the one reported by the runtime before boot.

Example fix

// before: served mismatched files
//   index.html -> dotnet.runtime.js (v8.0.7)
//   bin/        -> assemblies from 8.0.4

// after: redeploy from one publish
//   dotnet new wasmbrowser -o app && dotnet publish app -c Release
//   serve app/bin/Release/net8.0/browser-wasm/publish/ as a unit
Defensive patterns

Strategy: validation

Validate before calling

// Before dotnet.create(), confirm the loader and runtime artifacts come from one build.
async function assertBuildConsistent() {
  const loaderHash = (await import('consts:gitHash')).default; // stamped into loader bundle
  // The runtime stamps its git hash onto DOTNET_INTEROP once booted; check after create()
  // by reading runtimeApi.runtimeBuildInfo.gitHash and comparing to loaderHash.
  return loaderHash; // compare post-boot; mismatch -> redeploy.
}

// In CI: pin all dotnet.* artifacts to the same version path on your CDN.

Try / catch

try {
  await dotnet.create();
} catch (e) {
  if (e instanceof Error && /Mismatched git hashes/i.test(e.message)) {
    throw new Error('Redeploy all dotnet wasm artifacts from a single publish; clear CDN/browser cache.', { cause: e });
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling dotnet.create()/dotnetInitializeModule with a mix of artifacts from different dotnet wasm builds - e.g. a newer dotnet.runtime.js paired with an older dotnet.native.wasm/managed assemblies, or vice versa. Reached at native/index.ts:42 when both hashes are present and unequal.

Common situations: Caching/bundling only part of the runtime output after a `dotnet publish` (e.g. updating the JS bundle but keeping old wasm/bin). Copying dotnet.js from one SDK and the assemblies from another. CDN mismatch where the JS and the wasm are pinned to different versions.

Related errors


AI-assisted analysis of dotnet/runtime@60108ba66e (2026-08-10). Data as JSON: /api/errors/1d770f20d87d260e. Report an issue: GitHub.

Appendix: source

Thrown at src/native/libs/System.Runtime.InteropServices.JavaScript.Native/native/index.ts:42

    _ems_.dotnetRuntimeExports.resolveOrRejectPromise(args);
}

export function SystemInteropJS_CancelPromise(taskHolderGCHandle: GCHandle): void {
    _ems_.dotnetRuntimeExports.cancelPromise(taskHolderGCHandle);
}

export function SystemInteropJS_InvokeJSFunction(functionJSSHandle: JSHandle, args: JSMarshalerArguments): void {
    _ems_.dotnetRuntimeExports.invokeJSFunction(functionJSSHandle, args);
}

export const gitHash = GitHash;
export function dotnetInitializeModule(internals: InternalExchange): void {
    if (!Array.isArray(internals)) throw new Error("Expected internals to be an array");
    const runtimeApi = internals[InternalExchangeIndex.RuntimeAPI];
    if (typeof runtimeApi !== "object") throw new Error("Expected internals to have RuntimeAPI");

    if (runtimeApi.runtimeBuildInfo.gitHash && runtimeApi.runtimeBuildInfo.gitHash !== _ems_.DOTNET_INTEROP.gitHash) {
        throw new Error(`Mismatched git hashes between loader and runtime. Loader: ${runtimeApi.runtimeBuildInfo.gitHash}, DOTNET_INTEROP: ${_ems_.DOTNET_INTEROP.gitHash}`);
    }

    internals[InternalExchangeIndex.InteropJavaScriptExportsTable] = interopJavaScriptExportsToTable({
        SystemInteropJS_GetManagedStackTrace: (args: JSMarshalerArguments) => _ems_._SystemInteropJS_GetManagedStackTrace(args),
        SystemInteropJS_CallDelegate: (args: JSMarshalerArguments) => _ems_._SystemInteropJS_CallDelegate(args),
        SystemInteropJS_CompleteTask: (args: JSMarshalerArguments) => _ems_._SystemInteropJS_CompleteTask(args),
        SystemInteropJS_ReleaseJSOwnedObjectByGCHandle: (args: JSMarshalerArguments) => _ems_._SystemInteropJS_ReleaseJSOwnedObjectByGCHandle(args),
        SystemInteropJS_BindAssemblyExports: (args: JSMarshalerArguments) => _ems_._SystemInteropJS_BindAssemblyExports(args),
        SystemInteropJS_CallJSExport: (methodHandle: CSFnHandle, args: JSMarshalerArguments) => _ems_._SystemInteropJS_CallJSExport(methodHandle, args),
    });
    function interopJavaScriptExportsToTable(map: InteropJavaScriptExports): InteropJavaScriptExportsTable {
        // keep in sync with interopJavaScriptExportsFromTable()
        return [
            map.SystemInteropJS_GetManagedStackTrace,
            map.SystemInteropJS_CallDelegate,
            map.SystemInteropJS_CompleteTask,
            map.SystemInteropJS_ReleaseJSOwnedObjectByGCHandle,
            map.SystemInteropJS_BindAssemblyExports,

View on GitHub (pinned to 60108ba66e)