Repository navigation
0.41.3 OMP harness detection never fires on bun-global installs (createRequire probe + ESM-only pi-utils) #425
Description
Activity
Follow-up: exact kill chain to the empty dashboard (verified end-to-end)
Traced one level deeper than the detection failure, into
packages/dashboard/src-tauri/src/{pi_sessions,db}.rs:- Plugin runs as
harness=pi(this issue) → writessession_projectsrows keyed(session_id, harness=pi)— 28 rows, correctproject_path. Zeroomprows. - Dashboard
list_omp_sessionsdoes scan OMP files correctly (scan_omp_session_dir→ 27 validtype=sessionheaders replicated locally) — but builds each row withlookup_session_identity(map, Harness::Omp, id), which misses (keys arepi) →project_identity = "". session_matches_filterdrops every""-identity row on any project page; project cards never form. Net: OMP sessions invisible in All, Pi, and OMP views (Pi tab scans~/.pi/agent/sessions, which does not exist on OMP-only machines).
Local bridge (verified):
INSERT OR REPLACE INTO session_projects SELECT session_id,'omp',project_path,updated_at ... WHERE harness='pi'— OMP sessions immediately appear under correct projects after dashboard Refresh. Memories/dreamer already worked (they join onsession_id, harness-agnostic orpi-matched).Suggested dashboard hardening alongside the detection fix: on
(harness, id)identity miss, fall back to an exact full-session_idmatch across harnesses for display identity (session IDs are full UUID-shaped; the(harness, session_id)PK exists because short prefixes may collide, so keep the PK — only the display lookup needs the fallback). That one change would have kept every OMP session visible despite the mislabeling.- Plugin runs as
Same bug kills historian on OMP hosts (all 215 runs, every model)
The harness mislabel has a second, louder victim.
buildArgskeys the historian subprocess flags offisOmpHostProcess()(=resolvePiHarnessKind() === "omp"):...ompHost ? ["--no-rules"] : ["--no-prompt-templates", "--no-context-files"]
On a bun-global OMP install the label is
"pi", so historian spawns the omp binary itself (resolvePiInvocationcorrectly resolves the runningompCLI) with upstream-Pi flags. omp 18.x renamed those to--no-rules:$ omp --print --mode json --no-session --no-skills --no-prompt-templates --no-context-files -p hi Error: unknown flags: --no-prompt-templates, --no-context-files # exit 2, no agent_end $ omp ... --no-rules --model bogus -p hi Model "bogus-model-xyz" not found # parses fine, fails later as expectedDashboard shows 215 consecutive
pi exited (code=2) ... unknown flagsfailures, identical across 4 models — model-independent, as expected for an argv bug.Robustness suggestion: key the flag set off the resolved target binary (
resolvePiInvocationalready knows it) rather than the host label — the two can disagree exactly when detection is broken, and the binary is what parses argv. Same forgetHostAgentSettingsDir/model-ref translation if cheap to do.Third layer: broker-style argv bypasses host-path fixes entirely
One more finding while fixing this locally. Even with the
@oh-my-piscope made resolvable from~/.bun/bin, detection still returns"pi"when the host is launched broker-style, i.e.argv[1] = .../install/global/node_modules/@oh-my-pi/pi-coding-agent/dist/cli.js(real path, observed on a live worker). That lookup chain reaches the realpi-utils— and then fails on its ESM-only exports map (exports["."]={types, import}, norequire/main), because the probe usescreateRequire().resolve():createRequire("<global>/pi-coding-agent/dist/cli.js").resolve("@oh-my-pi/pi-utils") → MODULE_NOT_FOUND (Require stack shows it found the package dir, then failed entry resolution)So Layer 2 bites on every install layout, not just bun-global ones. Local bridge: add
"require": "./src/index.ts"to the installedpi-utilsexports (bun transpiles the TS source on require; ESMimportcondition untouched; CLI still boots). With that, all three requester styles reportharness=ompand historian/dreamer get--no-rules.Suggests the fix should avoid
require.resolvefor the probe entirely (dynamicimport()of the package, or no probe at all — see previous comment about keying flags off the resolved target binary).- added a commit that references this issue
on Sep 6, 2026 Thanks for the unusually complete diagnosis — both failure layers check out.
The 0.41.3 probe (
packages/pi-plugin/src/pi-harness-kind.ts) usedcreateRequire(requester).resolve("@oh-my-pi/pi-utils")fromprocess.argv[1]andprocess.execPath, then readAPP_NAMEfrom the resolved module. On the bun-global layout the shim directory cannot seeinstall/global/node_modules, and even from the real host entry the CJS resolver never matches animport-only exports map, so the probe threw and the fallback filed every session as Pi. A regression fixture now mirrors your layout (bin/ompsymlink intoinstall/global/node_modules/@oh-my-pi/pi-coding-agent/dist/cli.jsplus an ESM-only@oh-my-pi/pi-utils) and fails on 0.41.3's detector.The replacement detector is a ladder:
- The host's own process identity: OMP calls
setProcessName(APP_NAME)before loading extensions, which setsprocess.titletoomp. This decides on every OMP 18.x install regardless of layout. - The host package name: realpath the host entry, walk up to the nearest
package.json, and classify@oh-my-pi/pi-coding-agentas OMP (Pi's@earendil-works/pi-coding-agentand the legacy@mariozechner/...name as Pi). - An ESM probe: locate
@oh-my-pi/pi-utilsrelative to the resolved host entry, take itsimporttarget, dynamic-import()it, and readAPP_NAME. - The launcher basename (
omp,oh-my-pi) through the same executable vocabulary process detection already uses; then default to Pi.
Detection is memoized per process, spawns nothing, and the boot line now records the deciding rung (
harness=omp (via process-title)), so a future mismatch is diagnosable from the log alone.The detected value is installed as the shared harness at plugin boot, and everything downstream keys off it:
historian.omp/dreamer.ompmodel resolution, the per-harness log path (the OMP doctor now reads the OMP path — it was reading Pi's), newsession_metarows, and the dashboard's OMP scanner labels. Sessions already stored asharness='pi'are not relabeled; they stay visible under the Pi filter as you found.Addendum after your second and third comments: thank you for tracing both follow-on kill chains. The historian runner now chooses flags, relative settings paths, tool names, and model-reference translation from the resolved child CLI package rather than the host label, so an OMP target gets
--no-rulesand a Pi target gets--no-prompt-templates --no-context-fileseven if detection regresses. Dashboard display lookup now falls back across harnesses only on an exact full session ID, without changing the(harness, session_id)key or migrating old rows, so pre-fix OMP sessions recorded as Pi remain visible. We also confirmed the detection ladder contains no CJS resolver probe and extended the bun-global fixture to cover the broker-style real.../pi-coding-agent/dist/cli.jsargv shape. All of it — the detector and these follow-ups — is in v0.41.4, published today.- The host's own process identity: OMP calls
0.41.3 OMP harness detection never fires on bun-global installs
Symptom
After upgrading to 0.41.3 (OMP first-class harness), the Magic Context dashboard's OMP sessions view is permanently empty, and project cards look stale — even though OMP sessions are live and recording. All OMP sessions keep filing under
harness='pi':session_meta: 3040opencoderows, 57pirows (newest = live session, today), 0omprowsloaded v0.41.3 | harness=pi(on an OMP host)~/.omp/agent/sessions/— nothing lost, purely a labeling/filter mismatchEnvironment
bun install -g(~/.bun/bin/omp→../install/global/node_modules/@oh-my-pi/pi-coding-agent/dist/cli.js)@cortexkit/pi-magic-context@0.41.3@oh-my-pi/pi-utils@18.1.11, ESM-only (exports["."]={types, import: ./src/index.ts}, nomain/requirecondition;APP_NAME = "omp"insrc/dirs.ts)Root cause:
detectPiHarnessKind()fails twice on this layout(
dist/index-s0zw9ddk.js:createRequire(requester).resolve("@oh-my-pi/pi-utils")forrequesterin[resolve(process.argv[1]), resolve(process.execPath)], thenAPP_NAME === "omp"check,catch {}→"pi")Layer 1 — scope invisible from both requester paths. Both requesters live in
~/.bun/bin/; Node walks up from there, but the@oh-my-piscope only exists under~/.bun/install/global/node_modules. Reproduced in a fresh process:Layer 2 — even with the scope bridged, bare CJS resolve fails. After symlinking the scope into
~/.bun/bin/node_modules(confirmed inresolve.paths, and the file subpath resolves fine):CJS
require.resolvenever matches theimport-only exports condition, so the probe throws inside thecatch {}and detection returns"pi". This second layer fails on source checkouts too, not just global installs.Related prior art: #367 (bare-import vs
require.resolvedual failure for the same package family), #297 (OMP support).Suggested fix
Probe OMP-ness in a way that survives global installs and ESM-only
pi-utils, e.g. any of:import()-based probe instead ofcreateRequire().resolve(),pi-coding-agent/package.jsonname === "@oh-my-pi/pi-coding-agent") via the resolved entry realpath,process.argvexecutable name (omp/oh-my-pi) as the existingPI_IMAGE_NAMES/commandHasPiExecutablelogic already does elsewhere.Historical rows stay
pi-labeled regardless; only new sessions would file underomponce detection works.Local workarounds (for other reporters)
~/.bun/bin/node_modulesfixes Layer 1 but not Layer 2; a CJS wrapper would be needed to forceomplocally (not recommended — masks the real bug).