Claude Code's Codex Read Your Plugins Without Asking Permission

Codex Just Read Your Claude Code Plugins (Without Asking Permission)

Codex CLI 0.146.0 came out on July 29th with a line that looks like routine maintenance:

“Support Agent Plugins manifests, workspace plugin publishing, and additional plugin marketplaces for Amazon Bedrock and Claude Code.”

Three features in a single sentence, buried between session naming and thread forking. Easy to skip.

Don’t skip it. That last clause — “additional plugin marketplaces for … Claude Code” — means Codex now reads catalogs written for the competing agent. Not through a converter, not through a community bridge. Natively, as a first-class source.

So I did the obvious test: write a plugin, install it in both harnesses, and figure out exactly where it stops working.

The Two File Layouts

A Codex plugin is a directory with a manifest:

my-first-plugin/
├── .codex-plugin/
│   └── plugin.json
└── skills/
    └── hello/
        └── SKILL.md

And the plugin.json is almost insultingly small:

{
  "name": "my-first-plugin",
  "version": "1.0.0",
  "description": "Reusable greeting workflow",
  "skills": "./skills/"
}

Optional fields are pointers, not inline configuration: mcpServers points to ./.mcp.json, hooks to ./hooks/hooks.json, apps to ./.app.json.

Now compare that to a Claude Code plugin. Same skills format (SKILL.md), same .mcp.json, same concept of hooks. The manifest lives in .claude-plugin/ instead of .codex-plugin/. That’s the main difference: a directory name.

The catalogs diverge a bit further. Codex expects a marketplace at $REPO_ROOT/.agents/plugins/marketplace.json (or ~/.agents/plugins/marketplace.json for personal ones) — notice the .agents/ namespace, vendor-neutral, the same instinct that produced AGENTS.md. Claude Code expects .claude-plugin/marketplace.json, with an owner object and a metadata.pluginRoot that Codex’s schema doesn’t share.

Except Codex reads it anyway. The build docs describe $REPO_ROOT/.claude-plugin/marketplace.json as a legacy-compatible marketplace. Read that word again: legacy. OpenAI isn’t describing Anthropic’s format as a foreign standard to interoperate with. It’s describing it as an earlier version of its own.

What’s Portable Without Friction

Skills are portable. A SKILL.md is a markdown file with frontmatter and instructions — there’s nothing harness-specific in the prose. Put the same skills/ directory in either manifest and both agents load it.

Hooks are more portable than I expected. Codex passes PLUGIN_ROOT and PLUGIN_DATA to hook commands and — here’s the revealing detail — also sets CLAUDE_PLUGIN_ROOT and CLAUDE_PLUGIN_DATA “for compatibility with existing plugin hooks”. OpenAI put Anthropic’s environment variable names inside their own runtime. That’s not convergence by accident; that’s someone deciding the cost of migration had to be zero.

In other words: a plugin that’s skills plus hooks is already genuinely portable today. Rename a directory, or publish both.

Where It Breaks: The MCP Servers

The seam is bundled MCP servers, and it’s a path resolution problem.

If your plugin brings its own MCP server and references it relatively in .mcp.json"./bundled-mcp-server" — Codex resolves that path from the process’s working directory, not from the plugin root. Launch Codex from anywhere other than the plugin’s own folder and the server won’t start. That’s issue #22842, open since May against 0.130.0, with bug and plugins labels, still unassigned. The requested fixes — setting cwd to the plugin root, supporting a cwd field, adding a ${PLUGIN_DIR} substitution — are all reasonable and none landed.

The workaround is absolute paths in config.toml, which as the issue reporter clarifies is fine for you and useless for distribution. If you’re packaging a plugin for other people, an absolute path from your machine isn’t a plugin.

There’s a second failure mode, louder. There are users reporting that Codex auto-mirrors Claude Code marketplaces it finds on disk to ~/.codex/.tmp/marketplaces/ without being asked, and then tries to launch MCP servers from Claude-only plugins on startup — including ones using ${CLAUDE_PLUGIN_ROOT} inside .mcp.json, which doesn’t get substituted. Result: handshake errors on every launch, from plugins you never installed in Codex. That’s issue #19372, reported against 0.124.0. The workaround is a stanza of config per plugin:

[plugins."claude-mem@thedotmack"]
enabled = false

Which scales exactly as badly as it looks. (Note: the issue reporter attributes this to core-plugins/src/marketplace_upgrade.rs; I couldn’t confirm that specific code path in the current main, so take the root cause as reporter attribution and the symptom as reported.)

Publishing a Single Plugin for Both

Given everything above, the practical recipe as of 0.146.0:

  1. Put your actual content in skills/ and hooks/. Those are the portable layers. Everything else is packaging.
  2. Publish both manifests. .codex-plugin/plugin.json and .claude-plugin/plugin.json pointing to the same skills/ and hooks/ directories. That’s a dozen lines each; duplicating costs less than building a build step.
  3. Don’t bundle an MCP server yet if you want a single artifact that works on both. Reference remote servers, or document manual setup in config.toml, until #22842 lands.
  4. Publish the catalog twice too. .agents/plugins/marketplace.json for Codex, .claude-plugin/marketplace.json for Claude Code. Codex reads the second as legacy, so if you’re going to write one, write that — but the .agents/ path is where this is headed.
  5. Install with codex plugin marketplace add owner/repo, then codex plugin marketplace list to confirm. Codex caches installs at ~/.codex/plugins/cache/$MARKETPLACE_NAME/$PLUGIN_NAME/$VERSION/ — useful when you need to check what actually got downloaded.

The other half of 0.146.0 is workspace publishing: in the Codex app, Plugins > Created by you > Share, and there you pick workspace members or groups. That’s the enterprise distribution path, and it shipped. The public Plugin Directory didn’t — the docs still say self-serve publishing is “coming soon”. It’s worth keeping that distinction clear when someone tells you Codex already has a plugin marketplace.

The Part That Isn’t About File Paths

Here’s what keeps turning over in my head. For two years the lock-in question in this category was about the model: what weights did you marry into, how expensive is it to switch providers, what happens to your prompts.

That question is becoming boring, fast. Codex 0.146.0 now lists marketplaces from Amazon Bedrock and Claude Code alongside its own. The model layer is turning into a dropdown.

What isn’t a dropdown is the catalog your team installs from, the plugins your workflows depend on, the hooks wired into your CI. That accumulates. It has versions and owners and a blast radius when it changes. And whoever hosts that catalog — an owner/repo that your whole team ran marketplace add against — has something that sticks way harder than an API key.

OpenAI understands this perfectly, and that’s why “legacy-compatible” is doing so much work in that sentence. Reading the competitor’s format with zero migration cost isn’t generosity. It’s the cheapest way possible to make sure the catalog people converge on is their own.

Skills are portable. Hooks are portable. MCP servers are the seam, and in that seam next year’s entire story gets decided.And you, do you already have plugins installed on more than one agent? Are you maintaining two manifests or did you choose a harness and stick with it?