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]` deliberatelyView on GitHub (pinned to 9690622007)
Solutions
- Restart omp — the next process will load the current .node file and its matching sentinel.
- Do not reinstall; the disk is already consistent with the new version.
- 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
- Restart omp after any upgrade instead of continuing a long-lived session.
- Avoid rebuilding/reinstalling the native addon while a session is running.
- In supervisors, restart the process when a version sentinel mismatch is detected.
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
- Loaded ${candidate} but it does not expose the @oh-my-pi/pi-
- InvalidArg
- Unhandled API: ${api}
- Unhandled API in mapOptionsForApi: ${model.api}
- RPC chunk received before protocol negotiation
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/942de90c4faa47b7.
Report an issue: GitHub.