Skip to content

fix(codex): use global runtime launcher without symlinks#753

Open
ken-jo wants to merge 3 commits into
mksglu:nextfrom
ken-jo:fix/codex-global-runtime-pointer
Open

fix(codex): use global runtime launcher without symlinks#753
ken-jo wants to merge 3 commits into
mksglu:nextfrom
ken-jo:fix/codex-global-runtime-pointer

Conversation

@ken-jo
Copy link
Copy Markdown
Contributor

@ken-jo ken-jo commented Jun 1, 2026

What / Why / How

Fixes #752.

What:

This PR makes the Codex marketplace plugin a thin launcher for the runtime code. The Codex marketplace package still exposes real discovery files (.codex-plugin/plugin.json, .codex-plugin/mcp.json, .codex-plugin/hooks.json), but MCP and hook commands now go through .codex-plugin/global-runtime.mjs.

Why:

The previous Windows marketplace failure came from relying on a Git-tracked symlink shim (plugins/context-mode -> ..). Native Windows checkouts can materialize that shim as a regular file containing .., which makes Codex install fail with missing plugin.json. The current url: "./" marketplace layout fixes discovery by avoiding the symlink, but Codex still benefits from a single global runtime model: users should be able to run one npm install -g context-mode and have Codex use the same runtime as the other supported hosts.

How:

  • Add .codex-plugin/global-runtime.mjs.
  • Change .codex-plugin/mcp.json to launch node ./.codex-plugin/global-runtime.mjs mcp.
  • Change .codex-plugin/hooks.json to launch node "${PLUGIN_ROOT}/.codex-plugin/global-runtime.mjs" hook <event>.
  • Resolve runtime roots in this order:
    • CONTEXT_MODE_GLOBAL_ROOT
    • CONTEXT_MODE_RUNTIME_ROOT
    • npm prefix / Windows %APPDATA%\npm\node_modules\context-mode / standard Unix global locations
    • npm root -g during MCP startup
    • the materialized plugin cache as fallback
  • Avoid spawning npm root -g on every hook call; hooks stay on explicit env vars, standard locations, and cache fallback.
  • Set CONTEXT_MODE_PLATFORM=codex before delegating.
  • Expose CONTEXT_MODE_EFFECTIVE_ROOT for debugging.
  • Fail open for hook runtime resolution errors by emitting the minimal Codex hook output for the event.

This keeps Codex marketplace installation symlink-free while allowing a global npm install to be the single source of runtime truth.

Affected platforms

  • Claude Code
  • Cursor
  • VS Code Copilot (GitHub Copilot)
  • JetBrains Copilot
  • Gemini CLI
  • Qwen Code
  • OpenCode
  • KiloCode
  • Codex CLI
  • OpenClaw (Pi Agent)
  • Pi
  • Kiro
  • Antigravity
  • Zed
  • All platforms

Test plan

  • Added tests/plugins/codex-global-runtime.test.ts for env override, npm-global discovery, Windows %APPDATA%\npm\node_modules\context-mode discovery, plugin-cache fallback, entry path generation, and Codex hook event name mapping.
  • Updated tests/plugins/codex-manifest.test.ts so Codex MCP and hooks are pinned to the new global runtime shim contract.
  • Re-ran the Codex marketplace layout contract to ensure the symlink-free marketplace source remains intact.
  • Validated on native Windows with a real tarball install:
    • npm pack --pack-destination C:\tmp\context-mode-pack-test
    • npm install -g C:\tmp\context-mode-pack-test\context-mode-1.0.161.tgz
    • verified global runtime selection returns source: "APPDATA npm" and root C:\Users\ikcha\AppData\Roaming\npm\node_modules\context-mode
    • ran the installed global shim for hook pretooluse
    • smoke-tested global-runtime.mjs mcp remains alive without stderr
    • ran context-mode doctor and confirmed storage, server test, FTS5/SQLite, and npm version checks pass

Commands run:

pnpm exec vitest run tests/plugins/codex-manifest.test.ts tests/plugins/codex-global-runtime.test.ts tests/codex/marketplace-layout.test.ts
pnpm exec tsc --noEmit
node --check .codex-plugin\global-runtime.mjs
git diff --check
npm pack --pack-destination C:\tmp\context-mode-pack-test
npm install -g C:\tmp\context-mode-pack-test\context-mode-1.0.161.tgz
context-mode doctor

Checklist

  • Tests added/updated (TDD: red → green)
  • npm test passes
  • npm run typecheck passes
  • Docs updated if needed (README, platform-support.md)
  • No Windows path regressions (forward slashes only)
  • Targets next branch (unless hotfix)
Cross-platform notes

Our CI runs on Ubuntu, macOS, and Windows.

  • Path construction in the launcher uses path.join() / path.resolve().
  • Codex manifest command strings keep ${PLUGIN_ROOT} only where Codex hook command interpolation already exists; MCP args still avoid placeholders because Codex does not expand them.
  • Windows global npm discovery uses %APPDATA%\npm\node_modules\context-mode as a standard location and was validated with an actual tarball global install.
  • Hook startup avoids shell-dependent path probing and does not run npm root -g on every hook invocation.
  • The materialized plugin cache remains a fallback for marketplace-only installs and non-standard global layouts.

Add a Codex marketplace launcher that prefers the global npm context-mode runtime and falls back to the materialized plugin cache so Windows installs avoid git-tracked symlink shims.

Constraint: keep Codex discovery files real while only delegating MCP and hooks at runtime

Tested: pnpm exec vitest run tests/plugins/codex-manifest.test.ts tests/plugins/codex-global-runtime.test.ts tests/codex/marketplace-layout.test.ts

Tested: pnpm exec tsc --noEmit

Tested: npm pack plus npm install -g tarball on Windows; APPDATA npm runtime selected and hook/MCP smoke passed

Co-authored-by: OmX <omx@oh-my-codex.dev>
Copilot AI review requested due to automatic review settings June 1, 2026 12:38
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR introduces a Codex “global runtime” launcher shim so the marketplace plugin can delegate MCP + hooks execution to a globally installed context-mode runtime when available, while retaining a plugin-cache fallback.

Changes:

  • Updated Codex MCP + hooks manifests to invoke .codex-plugin/global-runtime.mjs instead of direct entrypoints.
  • Added a new .codex-plugin/global-runtime.mjs resolver/launcher that discovers a global runtime via env vars, standard npm-global locations, and (for MCP) npm root -g.
  • Expanded/added Vitest coverage for the new launcher behavior and updated documentation to describe the global-runtime mode.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tests/plugins/codex-manifest.test.ts Updates manifest expectations to validate the new global-runtime shim wiring.
tests/plugins/codex-global-runtime.test.ts Adds focused tests for runtime root discovery and entrypoint mapping.
docs/platform-support.md Documents the “thin launcher” behavior and global runtime resolution order.
README.md Adds user-facing explanation of global runtime mode and fallback behavior.
.codex-plugin/mcp.json Switches MCP server args to run the global-runtime shim.
.codex-plugin/hooks.json Switches hook commands to route through the global-runtime shim.
.codex-plugin/global-runtime.mjs Implements runtime discovery + delegation for MCP and hooks.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +7 to +8
const REPO_ROOT = resolve(__dirname, "..", "..");
const runtimeModule = await import(pathToFileURL(resolve(REPO_ROOT, ".codex-plugin/global-runtime.mjs")).href);
Comment on lines +88 to +90
if (platform === "win32") {
pushUnique(candidates, seen, join(env.APPDATA || "", "npm", "node_modules", PACKAGE_NAME), "APPDATA npm");
} else {
Comment thread .codex-plugin/global-runtime.mjs Outdated
Comment on lines +33 to +41
function pushUnique(candidates, seen, rawPath, source) {
const path = trimPath(rawPath);
if (!path) return;
const root = resolve(path);
const key = root.toLowerCase();
if (seen.has(key)) return;
seen.add(key);
candidates.push({ root, source });
}
Comment thread .codex-plugin/global-runtime.mjs Outdated
Comment on lines +91 to +92
pushUnique(candidates, seen, join(homedir(), ".npm-global", "lib", "node_modules", PACKAGE_NAME), "home npm-global");
pushUnique(candidates, seen, "/usr/local/lib/node_modules/context-mode", "usr-local npm");
@mksglu
Copy link
Copy Markdown
Owner

mksglu commented Jun 1, 2026

@ken-jo CI has error

@ken-jo
Copy link
Copy Markdown
Contributor Author

ken-jo commented Jun 1, 2026

@mksglu I'm fixing it

Address Copilot review by guarding empty APPDATA candidates, preserving case-sensitive path dedupe, and making runtime tests compare canonical macOS realpaths.

Constraint: keep hook startup free of npm root probes while preserving plugin-cache fallback

Tested: pnpm exec vitest run tests/plugins/codex-manifest.test.ts tests/plugins/codex-global-runtime.test.ts tests/codex/marketplace-layout.test.ts

Tested: pnpm exec tsc --noEmit

Tested: node --check .codex-plugin/global-runtime.mjs

Co-authored-by: OmX <omx@oh-my-codex.dev>
@ken-jo
Copy link
Copy Markdown
Contributor Author

ken-jo commented Jun 1, 2026

Follow-up commit 551b871 addresses the Copilot review and the macOS CI failure:

  • Removed the ESM-incompatible __dirname usage from the Vitest runtime test by deriving the test directory from import.meta.url.
  • Guarded Windows APPDATA runtime discovery so an empty APPDATA value cannot create a relative npm/node_modules/context-mode candidate.
  • Made runtime candidate de-duplication platform-aware, preserving case-sensitive paths on non-Windows platforms while keeping case-insensitive matching on Windows.
  • Replaced the hard-coded context-mode path fragment in the /usr/local global candidate with PACKAGE_NAME.
  • Updated runtime tests to compare canonical realpaths, which covers the macOS /var -> /private/var behavior seen in CI.

Local verification:

pnpm exec vitest run tests/plugins/codex-manifest.test.ts tests/plugins/codex-global-runtime.test.ts tests/codex/marketplace-layout.test.ts
pnpm exec tsc --noEmit
node --check .codex-plugin/global-runtime.mjs
git diff --check

@NgoQuocViet2001
Copy link
Copy Markdown
Contributor

@mksglu @ken-jo tested this on Windows.

Ran the PR test set + extra Codex hook/routing coverage:

  • 25/25 PR tests passed
  • 168 passed / 2 skipped Codex hook tests
  • pnpm exec tsc --noEmit, node --check .codex-plugin/global-runtime.mjs, git diff --check, and pnpm run build passed

Manual Codex install with a fresh temp CODEX_HOME also passed: marketplace add/install created the plugin cache cleanly, no missing plugin.json. Installed launcher resolves MCP + PreToolUse to npm global via APPDATA npm, and cache fallback works when global roots are absent.

Looks good on my Windows side.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants