Skip to content

feat(dev-tooling): run the plugin on OpenAI Codex - #190

Merged
Martin Bens (SpiGAndromeda) merged 12 commits into
mainfrom
feat/dev-tooling-codex-support
Oct 8, 2026
Merged

Martin Bens (SpiGAndromeda) merged 12 commits into
mainfrom
feat/dev-tooling-codex-support

Conversation

@SpiGAndromeda

@SpiGAndromeda Martin Bens (SpiGAndromeda) commented Oct 8, 2026 •

Copy link
Copy Markdown
Collaborator

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.json lists dev-tooling for codex plugin marketplace add shopwareLabs/ai-coding-tools. Codex reads it in place of .claude-plugin/marketplace.json, so only dev-tooling is listed there.
  • plugins/dev-tooling/.codex-plugin/plugin.json carries the same name, version and metadata as the Claude Code manifest, except a description and keywords without the LSP, and points mcpServers at codex.mcp.json. hooks/hooks.json is discovered without a hooks field.
  • codex.mcp.json starts the three servers with relative commands and cwd: ".", 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, TMPDIR and a few more) plus only the variables an entry declares, so each entry forwards PROJECT_ROOT, its MCP_*_TOOLING_CONFIG override, MCP_LOG_STDERR, MCP_EXTRA_LOG_FILE, the DOCKER_* 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) and SSH_AUTH_SOCK.

Project root on Codex

With cwd: "." a server starts in the plugin directory and receives no project directory. shared/launch.sh starts such a server unbound: it loads no configuration, answers initialize and tools/list, and every tool except set_project_root and cwd refuses with a message that names set_project_root. cwd reports the unbound state.

While unbound, set_project_root accepts 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 relative MCP_*_TOOLING_CONFIG resolves 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_ROOT before 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_root with 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_CONFIG is 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, so phpstan_analyze ran 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_init into worktree_launch_common_init, which each server.sh calls when the server starts bound. The set_project_root description in all three tools.json files now covers both cases.

Hooks

  • The hook library resolves the project directory from CLAUDE_PROJECT_DIR and, when that is unset as on Codex, from the hook input's .cwd. The library is template-synced, so templates/hooks-shared/common.sh and shopware-env's copy carry the same change.
  • Block messages name the tool and its server (they read "Use phpstan_analyze on the php-tooling MCP server instead!") instead of mcp__<server>__<tool>, a form that neither host produces for a plugin install. This covers shopware-env's check-lifecycle-tools.sh as well.
  • The PostToolUse matcher of the PHPStan baseline hook never matched on Claude Code plugin installs, which name the tool mcp__plugin_dev-tooling_php-tooling__phpstan_analyze. It now matches that name and the Codex name mcp__php_tooling__phpstan_analyze.
  • Codex gets its own SessionStart directives (mcp-tool-directives-codex.md), selected when CLAUDE_PROJECT_DIR is unset. They leave out the dev-tooling-runner subagent and EnterWorktree/ExitWorktree, which Codex does not have. On that path the hook appends a section that names the session directory from the hook input's .cwd and tells the model to call set_project_root with it when a tool reports no project root; that section is emitted even when enforce_mcp_tools is false in every config present.
  • The Claude Code directives now name the server of every tool they mention. Both directive files say to run worktree_prepare on the server a refusal names, since a ludtwig refusal for a missing vendor/ names php-tooling.

Claude Code only

The phpactor LSP (.lsp.json), the dev-tooling-runner agent and the EnterWorktree/ExitWorktree reminder hook have no Codex equivalent and are documented as Claude Code only.

Other changes

  • plugin-updating bumps .codex-plugin/plugin.json together with .claude-plugin/plugin.json, through one rewrite function, and checks the rewritten version of every manifest it touches.
  • CI runs the BATS job when only .agents/** or .claude-plugin/** changes.
  • The root README.md lists bash 4.1+ (macOS's stock /bin/bash is 3.2) among dev-tooling's prerequisites.
  • Releases: dev-tooling 4.1.0, shopware-env 1.4.1 (block messages and the .cwd fallback), plugin-setup 1.0.3 (refreshed copy of dev-tooling's SETUP.md).

Related: #189 (merged config temp file left behind at server start).

🤖 Generated with Claude Code

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>
@github-actions

github-actions Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

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>
@SpiGAndromeda
Martin Bens (SpiGAndromeda) merged commit 5cf1034 into main Oct 8, 2026
13 checks passed
@SpiGAndromeda
Martin Bens (SpiGAndromeda) deleted the feat/dev-tooling-codex-support branch October 11, 2026 21:18
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.

1 participant