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
- Reinstall/update both the extension and the windhawk-core DLL from the same release so versions align
- Verify the DLL's contractVersion matches CONTRACT_VERSION in dllBackend.ts (log info.contractVersion)
- 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
- Always ship extension and core DLL from the same release
- Bump CONTRACT_VERSION and regenerate/repackage together on ABI changes
- Log both versions at startup to catch skew early
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)