{"record":{"id":"14618a97aa15700f","repo":"can1357/oh-my-pi","slug":"loaded-candidate-but-it-does-not-expose-the-oh","errorCode":null,"errorMessage":"Loaded ${candidate} but it does not expose the @oh-my-pi/pi-natives@${ctx.packageVersion} version sentinel \\`${ctx.versionSentinelExport}\\`. The .node file on disk is from a different release than this loader — reinstall to re-sync.","messagePattern":"Loaded (.+?) but it does not expose the @oh-my-pi/pi-natives@(.+?) version sentinel \\\\`(.+?)\\\\`\\. The \\.node file on disk is from a different release than this loader — reinstall to re-sync\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/natives/native/loader-state.js","lineNumber":716,"sourceCode":"\ttry {\n\t\tdiskHasExpectedSentinel = fs.readFileSync(candidate).includes(ctx.versionSentinelExport);\n\t} catch {\n\t\t// The successful require above normally guarantees readability. If the\n\t\t// file disappears concurrently, retain the safe reinstall diagnosis.\n\t}\n\tif (isCompatiblePreSentinelNativeAddon(bindings, diskHasExpectedSentinel)) return;\n\tif (residentSentinel && diskHasExpectedSentinel) {\n\t\tconst residentVersion = residentSentinel.slice(\"__piNativesV\".length).replace(/_/g, \".\");\n\t\tthrow new Error(\n\t\t\t`Loaded ${candidate}, which exposes the @oh-my-pi/pi-natives@${residentVersion} version ` +\n\t\t\t\t`sentinel \\`${residentSentinel}\\` but not the @${ctx.packageVersion} sentinel ` +\n\t\t\t\t`\\`${ctx.versionSentinelExport}\\` this loader expects. omp was upgraded to ` +\n\t\t\t\t`${ctx.packageVersion} while this session was running; the ${residentVersion} addon is ` +\n\t\t\t\t\"still resident in this process. Disk is already consistent — restart omp to pick up \" +\n\t\t\t\t`${ctx.packageVersion} (reinstalling changes nothing).`,\n\t\t);\n\t}\n\tthrow new Error(\n\t\t`Loaded ${candidate} but it does not expose the @oh-my-pi/pi-natives@${ctx.packageVersion} ` +\n\t\t\t`version sentinel \\`${ctx.versionSentinelExport}\\`. The .node file on disk is from a different ` +\n\t\t\t\"release than this loader — reinstall to re-sync.\",\n\t);\n}\n\n/**\n * Install the addon's bounded Tokio runtime now that `dlopen` has returned and\n * the dynamic-loader lock is released. The Rust `#[module_init]` deliberately\n * does NOT build the runtime — spawning worker threads under the loader lock\n * deadlocks on some hosts — so it exposes `__ompInstallTokioRuntime` for the\n * loader to call once, before any async native runs. Best-effort: older addons\n * predating this export simply fall back to napi-rs's default runtime.\n */\nfunction installNativeTokioRuntime(bindings) {\n\tconst install = bindings.__ompInstallTokioRuntime;\n\tif (typeof install !== \"function\") return;\n\ttry {","sourceCodeStart":698,"sourceCodeEnd":734,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/natives/native/loader-state.js#L698-L734","documentation":"The loaded .node addon exposes no version sentinel export at all matching the expected @oh-my-pi/pi-natives@<version> sentinel, and it is neither the expected version nor a compatible pre-sentinel addon. The loader concludes the .node file on disk belongs to a different release than the JS loader — an installation inconsistency where the module itself is stale or foreign. The fix is to reinstall so loader and addon come from the same release.","triggerScenarios":"validateLoadedBindings is reached with no resident sentinel matching the expected version and diskHasExpectedSentinel false (the disk file also lacks the expected sentinel), so neither the restart-diagnosis nor the pre-sentinel compatibility path applies. Thrown as the final fallback of validateLoadedBindings.","commonSituations":"A partial upgrade left a mismatched .node file; mixing addon binaries from a different omp/pi-natives release (copied node_modules, monorepo hoisting, manually placed .node); corrupted download where the sentinel export is unreadable.","solutions":["Reinstall @oh-my-pi/pi-natives / omp so the .node file and JS loader come from the same release (rm -rf node_modules && install, or package reinstall).","Delete stale caches: the versioned native dir under the package and any copied addon binaries.","Ensure only one copy of the package exists in node_modules (check hoisting / lockfile duplicates).","If behind a proxy/mirror, clear the package cache and re-download (verify the tarball hash)."],"exampleFix":"// before\n$ bun install   # leaves stale mismatched .node\n// after\n$ rm -rf node_modules && bun install --force","handlingStrategy":"try-catch","validationCode":"import { createRequire } from \"node:module\";\nconst require = createRequire(import.meta.url);\nfunction checkAddonSentinel(candidatePath, versionSentinelExport) {\n  try {\n    return typeof require(candidatePath)?.[versionSentinelExport] === \"string\";\n  } catch (err) {\n    return false; // module won't load at all — reinstall needed\n  }\n}\nif (!checkAddonSentinel(diskPath, expectedSentinel)) promptReinstall();","typeGuard":null,"tryCatchPattern":"try {\n  loadNative(ctx);\n} catch (err) {\n  if (err.message.includes(\"does not expose the @oh-my-pi/pi-natives\")) {\n    // run clean reinstall: rm -rf node_modules && install --force\n  } else throw err;\n}","preventionTips":["Keep the JS loader and .node binary from the same release — never copy .node files between installs.","Use a single lockfile-managed copy of the package (avoid hoisting duplicates).","Reinstall cleanly (delete node_modules) after partial or failed upgrades.","Verify package tarball hashes when installing through mirrors."],"tags":["version-mismatch","native-addon","installation","corruption"],"backgroundTag":"native-addon-version-mismatch","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}