can1357/oh-my-pi · error · Error

Loaded ${candidate}, which exposes the @oh-my-pi/pi-natives@

Error message

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).

What it means

validateLoadedBindings successfully required the .node addon, but the resident module only exports the version sentinel of an older @oh-my-pi/pi-natives release, while the disk file does expose the current version's sentinel. This is the 'omp was upgraded while this process was running' case: the old .node binary is still resident (Node/Bun caches native modules per path and version), and the disk copy is already the new one. Restarting omp — not reinstalling — resolves it.

Source

Thrown at packages/natives/native/loader-state.js:707

	//     so reinstall is a no-op and only restarting the process re-syncs.
	const residentSentinel = Object.keys(bindings).find(
		key => key !== ctx.versionSentinelExport && /^__piNativesV[A-Za-z0-9_]+$/.test(key),
	);
	// A prior sentinel alone cannot distinguish a resident old module from an
	// actually stale file: `require` returns the same exports in both cases.
	// The restart diagnosis is valid only when the selected file itself carries
	// the current sentinel; otherwise a restart would simply reload stale disk.
	let diskHasExpectedSentinel = false;
	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

View on GitHub (pinned to 9690622007)

Solutions

  1. Restart omp — the next process will load the current .node file and its matching sentinel.
  2. Do not reinstall; the disk is already consistent with the new version.
  3. If it persists after restart, clear the module cache by starting a fresh shell/process (native modules cannot be unloaded in-process).
Defensive patterns

Strategy: retry

Validate before calling

// Before calling loadNative in long-lived processes, compare disk vs expected sentinel:
import { createRequire } from "node:module";
const require = createRequire(import.meta.url);
function diskAddonMatchesVersion(candidatePath, expectedSentinel) {
  try { return Boolean(require(candidatePath)[expectedSentinel]); } catch { return false; }
}
// if disk matches but a stale resident module is suspected -> plan a restart, don't reinstall

Try / catch

try {
  loadNative(ctx);
} catch (err) {
  if (err.message.includes("still resident in this process")) {
    // surface a friendly "restart omp to finish the upgrade" message; do NOT reinstall
  } else throw err;
}

Prevention

When it happens

Trigger: loadNative() → validateLoadedBindings() finds bindings whose sentinel export (e.g. __piNativesV0_1_2) matches neither the expected ctx.versionSentinelExport nor a pre-sentinel-compatible shape, yet isCompatiblePreSentinelNativeAddon fails AND the resident sentinel exists while the disk file has the expected sentinel. Only thrown when residentSentinel && diskHasExpectedSentinel both hold.

Common situations: Running a long-lived omp session while an upgrade (npm/bun install or auto-update) replaced the .node file on disk; a dev workflow rebuilding the addon mid-session.

Related errors


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