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

  1. 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).
  2. Delete stale caches: the versioned native dir under the package and any copied addon binaries.
  3. Ensure only one copy of the package exists in node_modules (check hoisting / lockfile duplicates).
  4. 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

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


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/14618a97aa15700f. Report an issue: GitHub.