Skip to content

0.41.3 OMP harness detection never fires on bun-global installs (createRequire probe + ESM-only pi-utils) #425

Description

@VibePDF

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: 3040 opencode rows, 57 pi rows (newest = live session, today), 0 omp rows
  • Plugin log on every boot: loaded v0.41.3 | harness=pi (on an OMP host)
  • 293 session files present under ~/.omp/agent/sessions/ — nothing lost, purely a labeling/filter mismatch

Environment

  • Host: OMP 18.1.1 installed via bun install -g (~/.bun/bin/omp → ../install/global/node_modules/@oh-my-pi/pi-coding-agent/dist/cli.js)
  • Plugin: @cortexkit/pi-magic-context@0.41.3
  • @oh-my-pi/pi-utils@18.1.11, ESM-only (exports["."] = {types, import: ./src/index.ts}, no main/require condition; APP_NAME = "omp" in src/dirs.ts)

Root cause: detectPiHarnessKind() fails twice on this layout

(dist/index-s0zw9ddk.js: createRequire(requester).resolve("@oh-my-pi/pi-utils") for requester in [resolve(process.argv[1]), resolve(process.execPath)], then APP_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-pi scope only exists under ~/.bun/install/global/node_modules. Reproduced in a fresh process:

createRequire("/home/sampath/.bun/bin/omp").resolve("@oh-my-pi/pi-utils")
→ MODULE_NOT_FOUND: Cannot find module '@oh-my-pi/pi-utils'
createRequire("<...>/bin/bun").resolve("@oh-my-pi/pi-utils")
→ MODULE_NOT_FOUND

Layer 2 — even with the scope bridged, bare CJS resolve fails. After symlinking the scope into ~/.bun/bin/node_modules (confirmed in resolve.paths, and the file subpath resolves fine):

require.resolve("@oh-my-pi/pi-utils/package.json") → OK (real file)
require.resolve("@oh-my-pi/pi-utils")              → MODULE_NOT_FOUND

CJS require.resolve never matches the import-only exports condition, so the probe throws inside the catch {} and detection returns "pi". This second layer fails on source checkouts too, not just global installs.

Related prior art: #367 (bare-import vs require.resolve dual 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:

  • ESM import()-based probe instead of createRequire().resolve(),
  • fall back to inspecting the host package (pi-coding-agent/package.json name === "@oh-my-pi/pi-coding-agent") via the resolved entry realpath,
  • or match process.argv executable name (omp/oh-my-pi) as the existing PI_IMAGE_NAMES/commandHasPiExecutable logic already does elsewhere.

Historical rows stay pi-labeled regardless; only new sessions would file under omp once detection works.

Local workarounds (for other reporters)

  • View OMP sessions under the PI filter — they are all there.
  • A scope symlink into ~/.bun/bin/node_modules fixes Layer 1 but not Layer 2; a CJS wrapper would be needed to force omp locally (not recommended — masks the real bug).

Activity

  1. VibePDF commented on Sep 5, 2026

    @VibePDF
    Author

    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:

    1. Plugin runs as harness=pi (this issue) → writes session_projects rows keyed (session_id, harness=pi) — 28 rows, correct project_path. Zero omp rows.
    2. Dashboard list_omp_sessions does scan OMP files correctly (scan_omp_session_dir → 27 valid type=session headers replicated locally) — but builds each row with lookup_session_identity(map, Harness::Omp, id), which misses (keys are pi) → project_identity = "".
    3. session_matches_filter drops 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 on session_id, harness-agnostic or pi-matched).

    Suggested dashboard hardening alongside the detection fix: on (harness, id) identity miss, fall back to an exact full-session_id match 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.

  2. added 2 commits that reference this issue on Sep 5, 2026
  3. VibePDF commented on Sep 5, 2026

    @VibePDF
    Author

    Same bug kills historian on OMP hosts (all 215 runs, every model)

    The harness mislabel has a second, louder victim. buildArgs keys the historian subprocess flags off isOmpHostProcess() (= 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 (resolvePiInvocation correctly resolves the running omp CLI) 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 expected
    

    Dashboard shows 215 consecutive pi exited (code=2) ... unknown flags failures, identical across 4 models — model-independent, as expected for an argv bug.

    Robustness suggestion: key the flag set off the resolved target binary (resolvePiInvocation already knows it) rather than the host label — the two can disagree exactly when detection is broken, and the binary is what parses argv. Same for getHostAgentSettingsDir/model-ref translation if cheap to do.

  4. VibePDF commented on Sep 5, 2026

    @VibePDF
    Author

    Third layer: broker-style argv bypasses host-path fixes entirely

    One more finding while fixing this locally. Even with the @oh-my-pi scope 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 real pi-utils — and then fails on its ESM-only exports map (exports["."] = {types, import}, no require/main), because the probe uses createRequire().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 installed pi-utils exports (bun transpiles the TS source on require; ESM import condition untouched; CLI still boots). With that, all three requester styles report harness=omp and historian/dreamer get --no-rules.

    Suggests the fix should avoid require.resolve for the probe entirely (dynamic import() of the package, or no probe at all — see previous comment about keying flags off the resolved target binary).

  5. added a commit that references this issue on Sep 6, 2026
  6. magic-alfonso commented on Sep 6, 2026

    @magic-alfonso

    Thanks for the unusually complete diagnosis — both failure layers check out.

    The 0.41.3 probe (packages/pi-plugin/src/pi-harness-kind.ts) used createRequire(requester).resolve("@oh-my-pi/pi-utils") from process.argv[1] and process.execPath, then read APP_NAME from the resolved module. On the bun-global layout the shim directory cannot see install/global/node_modules, and even from the real host entry the CJS resolver never matches an import-only exports map, so the probe threw and the fallback filed every session as Pi. A regression fixture now mirrors your layout (bin/omp symlink into install/global/node_modules/@oh-my-pi/pi-coding-agent/dist/cli.js plus an ESM-only @oh-my-pi/pi-utils) and fails on 0.41.3's detector.

    The replacement detector is a ladder:

    1. The host's own process identity: OMP calls setProcessName(APP_NAME) before loading extensions, which sets process.title to omp. This decides on every OMP 18.x install regardless of layout.
    2. The host package name: realpath the host entry, walk up to the nearest package.json, and classify @oh-my-pi/pi-coding-agent as OMP (Pi's @earendil-works/pi-coding-agent and the legacy @mariozechner/... name as Pi).
    3. An ESM probe: locate @oh-my-pi/pi-utils relative to the resolved host entry, take its import target, dynamic-import() it, and read APP_NAME.
    4. 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.omp model resolution, the per-harness log path (the OMP doctor now reads the OMP path — it was reading Pi's), new session_meta rows, and the dashboard's OMP scanner labels. Sessions already stored as harness='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-rules and a Pi target gets --no-prompt-templates --no-context-files even 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.js argv shape. All of it — the detector and these follow-ups — is in v0.41.4, published today.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions