Repository navigation
feat(dev-tooling): run the plugin on OpenAI Codex - #190
Merged
Martin Bens (SpiGAndromeda) merged 12 commits intoOct 8, 2026
Merged
Conversation
Claude Code names a plugin's MCP tools `mcp__plugin_<plugin>_<server>__<tool>` and compares a plain matcher string exactly, so the PostToolUse matcher `mcp__php-tooling__phpstan_analyze` never fired for plugin installs. The anchored matcher now accepts `mcp__plugin_dev-tooling_php-tooling__phpstan_analyze` and the name Codex gives the same tool, `mcp__php_tooling__phpstan_analyze`. Co-Authored-By: Claude <noreply@anthropic.com>
Codex sets no `CLAUDE_PROJECT_DIR` for plugin hooks, so the enforcement hooks found no `.mcp-*.json` config there. The new `resolve_project_dir` in the shared hook library takes `CLAUDE_PROJECT_DIR` when it is set and the hook input's `.cwd` otherwise. `load_mcp_config`, dev-tooling's `session-start.sh` and `check-phpstan-baseline.sh` share it, and each script still reads stdin once. The block messages named tools as `mcp__<server>__<tool>`, a form neither host uses. Claude Code prefixes plugin tools with `mcp__plugin_<plugin>_`, and Codex replaces the `-` in server names with `_`. `block_tool` now takes the server and the tool names and prints "Use `<tool>` on the `<server>` MCP server instead!". The library is template-synced, so `templates/hooks-shared/common.sh` and the shopware-env copy carry the same change, and the `block_tool` calls in shopware-env's `check-lifecycle-tools.sh` use the new signature. Co-Authored-By: Claude <noreply@anthropic.com>
Codex reads `.codex-plugin/plugin.json` before `.claude-plugin/plugin.json` and does not expand `${CLAUDE_PLUGIN_ROOT}` in MCP config, so the new manifest points `mcpServers` at `codex.mcp.json`. That file starts the three servers with `cwd: "."` (the plugin root) and relative commands, and forwards `PROJECT_ROOT`, the `MCP_*_TOOLING_CONFIG` overrides, the log variables, `DOCKER_*` and `SSH_AUTH_SOCK`, because Codex starts MCP servers from an otherwise cleared environment. `hooks/hooks.json` is still auto-discovered.
`.agents/plugins/marketplace.json` lists only dev-tooling. Codex reads it in place of `.claude-plugin/marketplace.json`, so plugins without Codex support stay hidden there. The Claude Code manifest, `.mcp.json` and marketplace are unchanged.
The SessionStart directives now name the server of every tool they mention.
Co-Authored-By: Claude <noreply@anthropic.com>
Codex starts plugin MCP servers with the plugin directory as their working directory and passes no project directory, so `PROJECT_ROOT` fell back to the plugin dir and `load_config` exited at startup. The new `shared/launch.sh` starts a server whose root is its own plugin directory, or lies inside it, without loading any configuration. `initialize` and `tools/list` work, and every tool except `set_project_root` and `cwd` refuses with a message that names `set_project_root`. In that state `set_project_root` accepts the absolute path of a plain project directory, loads its configuration (honoring `MCP_<PREFIX>_CONFIG`), validates scopes and environment, and binds the server to it once. Each request runs in its own subshell, so the bind is a launch record in the server's state file and every tool call rebuilds the bound state from it. A refused bind leaves the server unbound. After the bind, the existing linked-worktree rules apply unchanged. A server started in a project directory, as Claude Code starts it, behaves as before. On Codex the SessionStart hook tells the model to call `set_project_root` with the session directory when a tool reports no project root. Co-Authored-By: Claude <noreply@anthropic.com>
The dev-tooling README, reference and enforcement docs describe installing the plugin from this repository's Codex marketplace, trusting its hooks with `/hooks` (or `--dangerously-bypass-hook-trust` for `codex exec`), and binding each server with `set_project_root`. They list the phpactor LSP, the worktree hook and the `dev-tooling-runner` agent as Claude Code only, and show the block messages in their new form. shopware-env 1.4.1 records the changed block messages and the `.cwd` fallback of its hook. Its `AGENTS.md` now states that `block_tool` writes a plain-text message to stderr and exits 2. plugin-setup 1.0.3 ships the updated copy of dev-tooling's `SETUP.md`. Co-Authored-By: Claude <noreply@anthropic.com>
The SessionStart directives told the model to delegate dev-tool runs to the `dev-tooling-runner` subagent and to call `set_project_root` after `EnterWorktree`/`ExitWorktree`. Codex has no plugin subagents and no such tools. `session-start.sh` now emits `mcp-tool-directives.md` when `CLAUDE_PROJECT_DIR` is set, which Claude Code does for plugin hooks, and the new `mcp-tool-directives-codex.md` otherwise. The Codex prompt lists the same servers and tools, explains that a server without a project root refuses every tool except `set_project_root` and `cwd`, and covers worktrees through `project_root` and `worktree_prepare`. The Claude Code output is unchanged. Co-Authored-By: Claude <noreply@anthropic.com>
The Claude Code directives told the model to call `worktree_prepare` on the server that refused. A `ludtwig` tool on `js-storefront-tooling` refuses a fresh worktree's missing `vendor/` with "Call worktree_prepare on the php-tooling server", so that instruction sent the model to the wrong server. The directive now says to call it on the server the refusal names, as the Codex directives already do. Co-Authored-By: Claude <noreply@anthropic.com>
`bump-plugin-version.sh` now sets the `version` of `.codex-plugin/plugin.json` to the same value as `.claude-plugin/plugin.json` when a plugin has one, and exits 1 if the two differ afterwards. Step 3 of the skill describes it. Co-Authored-By: Claude <noreply@anthropic.com>
Agent Skills ExportBuilt 7 skill packages: |
An exported `PROJECT_ROOT` stays in the environment of Codex's long-lived app-server process, so later sessions it serves, including other projects, start bound to that path until the process restarts. The docs now recommend binding through `set_project_root` as the SessionStart directive instructs, and warn about the export. A new `codex_launch.bats` case checks that an unbound server refuses `phpstan_analyze` and `worktree_prepare` calls whose `project_root` names a linked worktree or the main checkout. Co-Authored-By: Claude <noreply@anthropic.com>
`set_project_root` with the launch root left the sticky worktree active on a Codex-bound server and was refused on a server started bound; it now clears the sticky root on both. A Codex bind stores the physical root, so a later `project_root` spelled canonically is not refused as a foreign worktree, and it resolves a relative `MCP_*_TOOLING_CONFIG` against that root instead of the plugin directory. `session-start.sh` keeps emitting its directives when the hook input is not JSON. The Codex manifest drops the LSP from its description and keywords, and the 4.1.0 CHANGELOG entries now describe changes against 4.0.0 only. New guard tests call every tool of each unbound server and expect the refusal. Co-Authored-By: Claude <noreply@anthropic.com>
A relative `MCP_*_TOOLING_CONFIG` resolved against whatever directory a call had entered, so a call targeting a linked worktree ran without the project's configuration. `launch_anchor_config_override` now makes it absolute at startup, on a Codex bind and on every later call after one. `codex.mcp.json` forwards the docker compose and Vagrant selection variables, and an unbound server answers `project_root: ""` with the empty-string error instead of asking for the call it just received. CI runs BATS when only a marketplace file changes, and `bump-plugin-version.sh` checks the Claude manifest rewrite for every plugin, not only those with a Codex manifest. Co-Authored-By: Claude <noreply@anthropic.com>
New cases cover the empty-string refusal on a server bound at launch and on one bound by `set_project_root`, require every `codex.mcp.json` server entry to forward the compose and Vagrant variables, and check that `js-admin-tooling` and `js-storefront-tooling` keep a relative `MCP_JS_TOOLING_CONFIG` on a call naming a linked worktree. Co-Authored-By: Claude <noreply@anthropic.com>
Martin Bens (SpiGAndromeda)
deleted the
feat/dev-tooling-codex-support
branch
October 11, 2026 21:18
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
dev-tooling now installs and runs on OpenAI Codex from this repository's marketplace. On Claude Code the servers start and resolve the project root as before, unless the session's project directory is the plugin directory or lies inside it; the hook and text changes below apply to both hosts.
Codex packaging
.agents/plugins/marketplace.jsonlistsdev-toolingforcodex plugin marketplace add shopwareLabs/ai-coding-tools. Codex reads it in place of.claude-plugin/marketplace.json, so onlydev-toolingis listed there.plugins/dev-tooling/.codex-plugin/plugin.jsoncarries the same name, version and metadata as the Claude Code manifest, except a description and keywords without the LSP, and pointsmcpServersatcodex.mcp.json.hooks/hooks.jsonis discovered without ahooksfield.codex.mcp.jsonstarts the three servers with relative commands andcwd: ".", which Codex resolves against the plugin root; Codex does not expand${CLAUDE_PLUGIN_ROOT}in this file. Codex starts MCP servers with a small default environment (HOME,PATH,SHELL,USER,TMPDIRand a few more) plus only the variables an entry declares, so each entry forwardsPROJECT_ROOT, itsMCP_*_TOOLING_CONFIGoverride,MCP_LOG_STDERR,MCP_EXTRA_LOG_FILE, theDOCKER_*variables, the docker compose selectors (COMPOSE_PROJECT_NAME,COMPOSE_FILE,COMPOSE_PROFILES,COMPOSE_PATH_SEPARATOR), the Vagrant settings (VAGRANT_CWD,VAGRANT_HOME,VAGRANT_DOTFILE_PATH,VAGRANT_VAGRANTFILE,VAGRANT_DEFAULT_PROVIDER) andSSH_AUTH_SOCK.Project root on Codex
With
cwd: "."a server starts in the plugin directory and receives no project directory.shared/launch.shstarts such a server unbound: it loads no configuration, answersinitializeandtools/list, and every tool exceptset_project_rootandcwdrefuses with a message that namesset_project_root.cwdreports the unbound state.While unbound,
set_project_rootaccepts the absolute path of a plain project directory, loads its configuration, validates scopes and environment, and binds the server to it once. The bind stores the physical path, and a relativeMCP_*_TOOLING_CONFIGresolves against that root. Each request runs in its own subshell, so the bind is a launch record in the server's state file, and every tool call restores the bound state from it. After the bind, the existing linked-worktree rules apply.The docs recommend this on-demand bind, which the SessionStart directive drives once the plugin hooks are trusted. Exporting
PROJECT_ROOTbefore starting Codex binds at startup instead. The docs warn that Codex's long-lived app-server process keeps an exported value, so later sessions it serves start bound to that path until the process restarts.On both hosts,
set_project_rootwith the launch root now clears the sticky root, as omitting the argument does. A server started with a project root refused that call before.A relative
MCP_*_TOOLING_CONFIGis now made absolute when the server starts or binds, and every later call sees the absolute path. Before, a server started in the project lost the override on a call targeting a linked worktree, sophpstan_analyzeran without the project's--configuration.To support the unbound start, the computation of the launch root's common git directory moved out of
worktree_state_initintoworktree_launch_common_init, which eachserver.shcalls when the server starts bound. Theset_project_rootdescription in all threetools.jsonfiles now covers both cases.Hooks
CLAUDE_PROJECT_DIRand, when that is unset as on Codex, from the hook input's.cwd. The library is template-synced, sotemplates/hooks-shared/common.shand shopware-env's copy carry the same change.phpstan_analyzeon thephp-toolingMCP server instead!") instead ofmcp__<server>__<tool>, a form that neither host produces for a plugin install. This covers shopware-env'scheck-lifecycle-tools.shas well.mcp__plugin_dev-tooling_php-tooling__phpstan_analyze. It now matches that name and the Codex namemcp__php_tooling__phpstan_analyze.mcp-tool-directives-codex.md), selected whenCLAUDE_PROJECT_DIRis unset. They leave out thedev-tooling-runnersubagent andEnterWorktree/ExitWorktree, which Codex does not have. On that path the hook appends a section that names the session directory from the hook input's.cwdand tells the model to callset_project_rootwith it when a tool reports no project root; that section is emitted even whenenforce_mcp_toolsis false in every config present.worktree_prepareon the server a refusal names, since aludtwigrefusal for a missingvendor/namesphp-tooling.Claude Code only
The phpactor LSP (
.lsp.json), thedev-tooling-runneragent and theEnterWorktree/ExitWorktreereminder hook have no Codex equivalent and are documented as Claude Code only.Other changes
plugin-updatingbumps.codex-plugin/plugin.jsontogether with.claude-plugin/plugin.json, through one rewrite function, and checks the rewritten version of every manifest it touches..agents/**or.claude-plugin/**changes.README.mdlistsbash4.1+ (macOS's stock/bin/bashis 3.2) among dev-tooling's prerequisites..cwdfallback), plugin-setup 1.0.3 (refreshed copy of dev-tooling'sSETUP.md).Related: #189 (merged config temp file left behind at server start).
🤖 Generated with Claude Code