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:
- Put your actual content in
skills/andhooks/. Those are the portable layers. Everything else is packaging. - Publish both manifests.
.codex-plugin/plugin.jsonand.claude-plugin/plugin.jsonpointing to the sameskills/andhooks/directories. That’s a dozen lines each; duplicating costs less than building a build step. - 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. - Publish the catalog twice too.
.agents/plugins/marketplace.jsonfor Codex,.claude-plugin/marketplace.jsonfor 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. - Install with
codex plugin marketplace add owner/repo, thencodex plugin marketplace listto 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?