{"record":{"id":"942de90c4faa47b7","repo":"can1357/oh-my-pi","slug":"loaded-candidate-which-exposes-the-oh-my-pi-p","errorCode":null,"errorMessage":"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).","messagePattern":"Loaded (.+?), which exposes the @oh-my-pi/pi-natives@(.+?) version sentinel \\\\`(.+?)\\\\` but not the @(.+?) sentinel \\\\`(.+?)\\\\` this loader expects\\. omp was upgraded to (.+?) while this session was running; the (.+?) addon is still resident in this process\\. Disk is already consistent — restart omp to pick up (.+?) \\(reinstalling changes nothing\\)\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/natives/native/loader-state.js","lineNumber":707,"sourceCode":"\t//     so reinstall is a no-op and only restarting the process re-syncs.\n\tconst residentSentinel = Object.keys(bindings).find(\n\t\tkey => key !== ctx.versionSentinelExport && /^__piNativesV[A-Za-z0-9_]+$/.test(key),\n\t);\n\t// A prior sentinel alone cannot distinguish a resident old module from an\n\t// actually stale file: `require` returns the same exports in both cases.\n\t// The restart diagnosis is valid only when the selected file itself carries\n\t// the current sentinel; otherwise a restart would simply reload stale disk.\n\tlet diskHasExpectedSentinel = false;\n\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","sourceCodeStart":689,"sourceCodeEnd":725,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/natives/native/loader-state.js#L689-L725","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Before calling loadNative in long-lived processes, compare disk vs expected sentinel:\nimport { createRequire } from \"node:module\";\nconst require = createRequire(import.meta.url);\nfunction diskAddonMatchesVersion(candidatePath, expectedSentinel) {\n  try { return Boolean(require(candidatePath)[expectedSentinel]); } catch { return false; }\n}\n// if disk matches but a stale resident module is suspected -> plan a restart, don't reinstall","typeGuard":null,"tryCatchPattern":"try {\n  loadNative(ctx);\n} catch (err) {\n  if (err.message.includes(\"still resident in this process\")) {\n    // surface a friendly \"restart omp to finish the upgrade\" message; do NOT reinstall\n  } else throw err;\n}","preventionTips":["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."],"tags":["version-mismatch","native-addon","hot-reload","restart-required"],"backgroundTag":"native-addon-version-mismatch","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}