can1357/oh-my-pi · error · Error
Loaded ${candidate} but it does not expose the @oh-my-pi/pi-
Error message
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. What it means
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.
Source
Thrown at packages/natives/native/loader-state.js:716
try {
diskHasExpectedSentinel = fs.readFileSync(candidate).includes(ctx.versionSentinelExport);
} catch {
// The successful require above normally guarantees readability. If the
// file disappears concurrently, retain the safe reinstall diagnosis.
}
if (isCompatiblePreSentinelNativeAddon(bindings, diskHasExpectedSentinel)) return;
if (residentSentinel && diskHasExpectedSentinel) {
const residentVersion = residentSentinel.slice("__piNativesV".length).replace(/_/g, ".");
throw new Error(
`Loaded ${candidate}, which exposes the @oh-my-pi/pi-natives@${residentVersion} version ` +
`sentinel \`${residentSentinel}\` but not the @${ctx.packageVersion} sentinel ` +
`\`${ctx.versionSentinelExport}\` this loader expects. omp was upgraded to ` +
`${ctx.packageVersion} while this session was running; the ${residentVersion} addon is ` +
"still resident in this process. Disk is already consistent — restart omp to pick up " +
`${ctx.packageVersion} (reinstalling changes nothing).`,
);
}
throw new Error(
`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.",
);
}
/**
* Install the addon's bounded Tokio runtime now that `dlopen` has returned and
* the dynamic-loader lock is released. The Rust `#[module_init]` deliberately
* does NOT build the runtime — spawning worker threads under the loader lock
* deadlocks on some hosts — so it exposes `__ompInstallTokioRuntime` for the
* loader to call once, before any async native runs. Best-effort: older addons
* predating this export simply fall back to napi-rs's default runtime.
*/
function installNativeTokioRuntime(bindings) {
const install = bindings.__ompInstallTokioRuntime;
if (typeof install !== "function") return;
try {View on GitHub (pinned to 9690622007)
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).
Example fix
// before $ bun install # leaves stale mismatched .node // after $ rm -rf node_modules && bun install --force
Defensive patterns
Strategy: try-catch
Validate before calling
import { createRequire } from "node:module";
const require = createRequire(import.meta.url);
function checkAddonSentinel(candidatePath, versionSentinelExport) {
try {
return typeof require(candidatePath)?.[versionSentinelExport] === "string";
} catch (err) {
return false; // module won't load at all — reinstall needed
}
}
if (!checkAddonSentinel(diskPath, expectedSentinel)) promptReinstall(); Try / catch
try {
loadNative(ctx);
} catch (err) {
if (err.message.includes("does not expose the @oh-my-pi/pi-natives")) {
// run clean reinstall: rm -rf node_modules && install --force
} else throw err;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Loaded ${candidate}, which exposes the @oh-my-pi/pi-natives@
- Failed to load pi_natives native addon for ${ctx.addonLabel}
- InvalidArg
- entry name contains NUL
- Persistent credential block store ${store} is unavailable af
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/14618a97aa15700f.
Report an issue: GitHub.