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
- Republish and redeploy all runtime artifacts together from a single `dotnet publish` (or matching SDK build) so loader and runtime share a git hash.
- Clear the browser/CDN cache of dotnet.runtime.js, dotnet.native.js, dotnet.native.wasm, and the managed DLLs, then reload.
- Pin your CDN/bundle versions explicitly (e.g. https://my.cdn/dotnet/8.0.7/...) so JS and wasm never drift.
- 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
- Publish and serve the entire dotnet wasm output as one unit (dotnet.runtime.js, dotnet.native.*, managed DLLs).
- Pin CDN paths to a specific SDK version so JS and wasm never drift.
- Bust browser/CDN cache for all dotnet.* files at once after a rebuild.
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
- Configuration environment variable is not defined
- ContinuousIntegrationBuild environment variable is not…
- ERR22
- Failed to load resource
- Invalid config, resources is not set
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)