ramensoftware/windhawk · critical

windhawk-core contract version mismatch: DLL has

Error message

windhawk-core contract version mismatch: DLL has ${info.contractVersion}, client expects ${CONTRACT_VERSION}

What it means

createDllBackend() loads the native bridge and reads its reported contractVersion, comparing it to the client's CONTRACT_VERSION constant. On mismatch it logs and throws, because the ABI between JS client and windhawk-core DLL is incompatible — this is treated as a packaging error that must fail loudly rather than corrupt state.

Solutions

  1. Reinstall/update both the extension and the windhawk-core DLL from the same release so versions align
  2. Verify the DLL's contractVersion matches CONTRACT_VERSION in dllBackend.ts (log info.contractVersion)
  3. Downgrade the extension or upgrade the core binary to the matching version pair

Example fix

null
Defensive patterns

Strategy: try-catch

Validate before calling

const info = bridge.getInfo?.();
if (info && info.contractVersion !== CONTRACT_VERSION) {
  console.error(`version skew: dll=${info.contractVersion} client=${CONTRACT_VERSION}`);
}

Type guard

const versionsMatch = (info: { contractVersion: number }): boolean => info.contractVersion === CONTRACT_VERSION;

Try / catch

try { const core = await createWindhawkCore(); } catch (e) { if (e.message.includes('contract version mismatch')) promptReinstallMatchingVersions(); else throw e; }

Prevention

When it happens

Trigger: Extension updated (or the DLL swapped) so the shipped windhawk-core DLL version differs from the CONTRACT_VERSION compiled into the client; createWindhawkCore is then called.

Common situations: Partial upgrade where extension and core DLL came from different versions; manually replacing the DLL with a dev build; a package manager pinned an old core.

Related errors


AI-assisted analysis of ramensoftware/windhawk@61d99ed8e1 (2026-09-12). Data as JSON: /api/errors/0b0f9e4b14fdba44. Report an issue: GitHub.

Appendix: source

Thrown at src/windhawk-vscode/src/coreClient/dllBackend.ts:263

// in-process fallback). `bridgeOverride` injects a fake bridge for tests (the
// production path loads the prebuilt .node from disk).
export function createDllBackend(options: DllBackendOptions, bridgeOverride?: BridgeModule): DllBackend {
	const { appRoot, windhawkVersion, userAgent, logger } = options;

	const bridge = bridgeOverride ?? loadBridgeFromDisk();

	// The bridge validates WhCoreGetAbiVersion itself; the contract version
	// is validated here, where the contract lives.
	const library = bridge.loadCore(resolveDllPath(appRoot));
	const info = JSON.parse(library.getInfoJson()) as { contractVersion: string };
	if (info.contractVersion !== CONTRACT_VERSION) {
		// Version skew must be loud: this is a packaging error, not a normal
		// missing-artifact development state.
		const message =
			`windhawk-core contract version mismatch: DLL has ${info.contractVersion}, ` +
			`client expects ${CONTRACT_VERSION}`;
		logger.error(message);
		throw new Error(message);
	}

	// In-flight async operations, keyed by the operation id the bridge
	// reports. JS is single-threaded and the bridge queues onEvent onto the
	// event loop, so a handler registered synchronously after invokeAsync
	// returns is in place before any of its operation's events can dispatch.
	const opHandlers = new Map<number, (event: OperationEvent) => void>();

	const session = library.createSession(
		JSON.stringify({
			appRootPath: appRoot,
			windhawkVersion,
			userAgent,
			debugOverrides: {
				modsUrlRoot: debugModsUrlRoot() ?? null,
				updateUrl: debugUpdateInstallerUrl() ?? null,
				installerRegKey: debugInstallerRegKeyString() ?? null,
				schtasksPath: debugSchtasksPath() ?? null,

View on GitHub (pinned to 61d99ed8e1)