Skip to content

feat: add support for Codex - #151

Draft
ryzizub wants to merge 18 commits into
mainfrom
feat/codex-support
Draft

feat: add support for Codex#151
ryzizub wants to merge 18 commits into
mainfrom
feat/codex-support

Conversation

@ryzizub

@ryzizub ryzizub commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Description

Wires up MCP, hooks, and the reviewer agent for Codex. Skills were already portable after #128. Closes #46.

Codex reads skills/, .mcp.json and hooks/hooks.json from the same files Claude Code uses, and resolves ${CLAUDE_PLUGIN_ROOT} as a compatibility alias, so almost nothing new is needed here.

What changes:

  • hooks/hooks.jsonPostToolUse matcher becomes apply_patch|Edit|Write. Codex names its edit tool apply_patch, so the hooks never fired there. Stays on Claude Code's exact-match path, so Edit and Write behave as before.
  • analyze.sh / format.sh — Codex passes the raw patch in tool_input.command with no file path anywhere, so both gain the same jq expression to read either shape. Claude Code behavior unchanged.
  • .codex-plugin/plugin.json — Codex plugin manifest. release-please bumps its version alongside the Claude one.
  • codex/agents/flutter-reviewer.toml — Codex cannot bundle a subagent in a plugin (openai/codex#18988), so users copy this file. Read-only is enforced by sandbox_mode rather than the agent-scoped hook Claude Code uses — same guarantee, different mechanism, worth a look.
  • Docs across README.md, CONTRIBUTING.md, CLAUDE.md, AGENTS.md.

Two of the issue's caveats are stale on Codex 0.153.4: the hooks flag is [features] hooks, on by default, and Codex does run hooks on Windows (our scripts need bash and jq, so WSL or Git Bash).

The marketplace entry that points here is handled separately in very-good-claude-code-marketplace.

Verification

Tested against a real Codex 0.153.4 in a throwaway CODEX_HOME. Installed as a plugin: 15/15 skills load, both MCP servers registered, codex doctor clean, hooks land at the discovery path.

Live hook runs: format.sh formats on a real apply_patch; analyze.sh exits 2 on one with a syntax error and the model reported the error back. The reviewer agent spawned, self-scoped via git diff, emitted the contract table, and wrote nothing.

Both hooks also checked by hand against the real Dart SDK. Existing suites pass (18, 25); cspell, markdownlint and claude plugin validate . clean.

Type of Change

  • New feature (feat)
  • Bug fix (fix)
  • Code refactor (refactor)
  • Documentation (docs)
  • CI change (ci)
  • Chore (chore)

🤖 Generated with Claude Code

Wires the MCP servers, enforcement hooks, and reviewer agent into Codex, so
the harness gets the whole plugin rather than only the skills it already reads
from `.agents/skills/`.

The hook scripts stay single-sourced. Codex passes `tool_name: "Bash"` with a
plain string command and accepts the same `permissionDecision` JSON, and plain
stdout from a SessionStart hook is injected the same way, so four of the six
scripts port with no edits. Only the edit hooks differ: Codex's file-editing
tool is `apply_patch` and it hands the hook a raw patch with no `file_path`, so
`hook-payload-common.sh` reads both payload shapes and `analyze.sh` /
`format.sh` stay identical across harnesses.

`codex/install.sh` installs skills, MCP servers, hooks, and agents, merging
into an existing `hooks.json` rather than overwriting it. `codex/loader_test.sh`
asserts Codex actually loads all of it via `codex debug prompt-input`, which
needs no credentials, and runs as a new `codex-loader` CI job.

The reviewer agent's read-only contract is held by `sandbox_mode = "read-only"`,
since Codex has no per-agent tool allowlist or agent-scoped PreToolUse hook.

Verified against Codex CLI 0.153.4. Hooks there are a stable, default-on
feature (`[features] hooks`) and do run on Windows, so the caveats in the issue
no longer apply; the Windows limit is that these scripts need bash and jq.

The Claude Code path is unchanged: `hooks/hooks.json` and
`agents/flutter-reviewer.md` stay authoritative and `codex/` mirrors them.

Closes #46

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ryzizub
ryzizub requested a review from a team as a code owner September 8, 2026 13:09
Two real problems the first CI run surfaced.

`codex doctor` reports `mcp.config` as a warning, not ok, when the server
executables are absent — the normal state on a runner with no Dart SDK. The
check now treats a warning as acceptable and asserts the registration itself
instead, so a warning can no longer hide a server pointing at the wrong
command.

`tomllib` needs Python 3.11, so the agent-file parsing died with a traceback on
older interpreters (macOS system Python is 3.9). The parser is now resolved
once in the preflight, falling back to tomli, with an actionable message when
neither is available.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ryzizub
ryzizub marked this pull request as draft September 8, 2026 13:17
ryzizub and others added 15 commits September 8, 2026 15:34
Codex has its own plugin system, so the installer was unnecessary. Adding
`.codex-plugin/plugin.json` and a marketplace entry in
`.agents/plugins/marketplace.json` makes this repo a Codex plugin, and the
install becomes the same two commands as Claude Code:

    codex plugin marketplace add VeryGoodOpenSource/vgv-ai-flutter-plugin
    codex plugin add vgv-ai-flutter-plugin@very-good-ventures

Codex then reads `skills/`, `.mcp.json`, and `hooks/hooks.json` from the very
files Claude Code uses, resolving `${CLAUDE_PLUGIN_ROOT}` as a compatibility
alias for the installed plugin directory. So `codex/hooks.json` and
`codex/config.toml` are gone — there is no second copy of the hooks and no MCP
block to keep in sync. The only change to the shared wiring is widening the
PostToolUse matcher to `apply_patch|Edit|Write`, which is inert on Claude Code.

A plugin cannot ship a Codex subagent: agents load only from `~/.codex/agents/`
or a project's `.codex/agents/`, and `agents` is neither a manifest field nor a
discovery path. `codex/agents/flutter-reviewer.toml` stays as a file users copy,
and it is all that is left in `codex/`.

release-please now bumps both manifests, and the loader test fails if their
versions drift. The loader test itself was rewritten to install the working tree
the way a user does and assert what Codex picked up: 35 assertions, up from 17.

Deletes codex/install.sh, codex/install_test.sh, codex/hooks.json,
codex/config.toml.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`codex plugin add` only takes PLUGIN@MARKETPLACE, and Codex silently drops any
marketplace entry that is not a `local` source resolving inside the marketplace
root. It does parse Claude Code's `.claude-plugin/marketplace.json`, but every
entry in very-good-claude-code-marketplace uses `source: github`, so adding that
marketplace to Codex yields "No marketplace plugins found" with no error.

Writing this down so nobody tries to move the entry there and hits a silent
dead end.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Codex marketplace entries do support a remote source — `{"source": "url", "url":
"..."}` — which I had missed. So there is no need for a second marketplace: the
entry belongs in very-good-claude-code-marketplace next to the Claude Code one,
pointing back at this repo, and `.agents/plugins/marketplace.json` is deleted
from here.

Codex resolves `url` and `local` sources only. It silently drops `github`,
`git`, `git-subdir`, and any path outside the marketplace root — the marketplace
adds fine and `codex plugin list` reports "No marketplace plugins found" with no
error. That is why the existing `.claude-plugin/marketplace.json` cannot serve
Codex as-is, and why the two manifests coexist in the marketplace repo. Written
up in CONTRIBUTING.md with the exact entry to add.

Because a `url` source always resolves the default branch, the loader test now
synthesizes its own throwaway marketplace pointing at the working tree, so CI
still tests the checkout rather than main. It also validates every field Codex
ingestion requires in .codex-plugin/plugin.json, which nothing else checked once
the scaffold validator was out of the picture: 44 assertions, up from 35.

Requires the companion entry in very-good-claude-code-marketplace before the
documented install command works.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`.codex-plugin/plugin.json` is not required. Codex falls back to
`.claude-plugin/plugin.json` for the plugin's name and version and discovers
`.mcp.json` on its own, so an install without it is indistinguishable from one
with it — same 15 skills, same `vgv-ai-flutter-plugin:<skill>` namespacing, same
two MCP servers, same `codex plugin list` output. Verified both ways.

What it would have added is Codex app presentation metadata (displayName,
category, capabilities, defaultPrompt, icons), which is not worth a second
manifest to keep version-synced. Removed from release-please's extra-files too.

The consequence is recorded in CONTRIBUTING.md and AGENTS.md:
`.claude-plugin/plugin.json` is now load-bearing for both harnesses rather than
Claude Code alone.

This repo now carries no Codex-specific plugin configuration at all. The only
Codex-only files left are the reviewer agent TOML and the loader test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Codex cannot bundle a subagent in a plugin, so the agent file is copied in. The
README only showed the personal scope (~/.codex/agents/); the project scope
(.codex/agents/ committed to the Flutter repo) is how most repos in the wild
actually do it and gives a whole team the reviewer with no per-developer setup.
Both are now documented, with links to the open upstream issues that would make
the step unnecessary.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A JSON round-trip while adding and then removing the Codex manifest entry
reflowed the file and escaped a non-ASCII character. Net change is zero, so
restore it byte-for-byte.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tomllib only appears in codex/loader_test.sh, which cspell does not check.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Verified this PR's assumptions against both hooks references. Three fixes.

`analyze.sh` was described as blocking. It is a PostToolUse hook, and Claude
Code's docs are explicit that exit 2 there does not block because the tool has
already run — stderr is shown to the model instead. The effect (the model sees
the analyzer output and fixes it) is what we wanted; the mechanism was
described wrong. `block-cli-workarounds.sh` is PreToolUse, where exit 2 really
does block, so those lines stand.

The claim that a Codex plugin manifest rejects a `hooks` field was overstated:
the scaffold validator refuses it, but the runtime docs say a manifest may
override the hooks path. Moot here since we ship no manifest, so the bullet now
just states that discovery defaults to <plugin root>/hooks/hooks.json.

Recorded the matcher trap. Claude Code treats a matcher of only letters, digits,
_, -, space, comma and | as exact tool names, and anything else as an unanchored
regex. `apply_patch|Edit|Write` stays on the exact path, which is why it matches
those three names and not MultiEdit. Adding a regex metacharacter would silently
pull in MultiEdit and NotebookEdit, whose payloads hook-payload-common.sh does
not read.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Replaces hooks/scripts/hook-payload-common.sh with the same six-line jq
expression in analyze.sh and format.sh. Codex hook payloads carry no file path
and no changed-file list, so the paths still have to come out of the patch
headers, but that no longer needs its own script.

Simpler than the helper it replaces: selecting only Add/Update/Move headers
means a delete contributes nothing, and a rename lists both paths so the
existing-file check drops the stale one. No Move-to bookkeeping, no awk.

Tests moved to dart-hooks_test.sh, which drives both hooks through a stub `dart`
on PATH and asserts exactly which files reach the SDK, plus the exit codes. That
covers more than the old helper-function tests did and still needs no Dart SDK
in CI. Also verified against the real SDK by hand.

Mutation testing while writing it found a genuine gap: removing the
"*** Begin Patch" guard did not fail the suite, because the ^ anchor already
rejects single-line shell commands. Added the cases that do catch it — a heredoc
whose body starts with a patch marker, and a marker on a later line.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two scripts is a deliberate maintainability choice over merging them. Notes the
two consequences so neither reads as a bug later: Claude Code runs hooks in a
matcher group in parallel, so the pair race on the same file, and the payload
jq is duplicated rather than shared.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Removes codex/loader_test.sh, hooks/scripts/dart-hooks_test.sh, and the
codex-loader CI job, per review: no new test suites, only the two existing ones.

Consequences, recorded in CONTRIBUTING.md rather than left implicit. CI no
longer exercises Codex at all, so a change to the hooks or codex/ has to be
verified by hand — the instructions for doing that replace the "run the loader
test" step. And the payload reading in analyze.sh and format.sh ships without
automated coverage, so the two jq copies have to be kept identical by review.

Also drops the cspell entries for words that only existed in the removed
documentation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Naming the two suites explicitly is fine now that this PR adds none, and the
change was unrelated to Codex.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
MultiEdit was removed from Claude Code in v2.0.8 and is absent from the current
tools documentation, so the matcher warning only needs to mention NotebookEdit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Drops the Claude Code comparison list. The mechanics it described belong in
CONTRIBUTING.md, which already covers them for contributors; the README section
now just says how to install and how to add the reviewer agent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds .codex-plugin/plugin.json so this repo declares itself as a Codex plugin
rather than relying on the fallback to .claude-plugin/plugin.json. It is also
the only place Codex app presentation metadata can live (displayName, category,
capabilities, defaultPrompt) and it points mcpServers at ./.mcp.json.

release-please now bumps the version in both manifests via extra-files, so they
cannot drift. The marketplace entry pointing here is handled in
very-good-claude-code-marketplace.

Verified: installs into a throwaway CODEX_HOME with 15/15 skills, both MCP
servers registered, and codex doctor reporting config.load and mcp.config ok.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ryzizub added a commit that referenced this pull request Sep 9, 2026
Matches the shape of the Codex PR (#151): the harness-specific assets are a
plain `gemini/` directory of files users copy, and everything else is docs.

- `.gemini/` becomes `gemini/`. It was never config for this repo's own
  workspace; it is the MCP + hooks settings a user merges into their own
  `~/.gemini/settings.json` (or a project's `.gemini/settings.json` for a whole
  team), and the reviewer agent they copy into `agents/` beside it. The hook
  path drops the `$PWD` fallback and reads `${VGV_PLUGIN_ROOT}` plainly, since
  the plugin is never installed at the user's project root.
- Drops the Gemini CLI loader CI job and `gemini-config_test.sh`. Nothing in CI
  exercises Codex either; both are verified by hand, and `CONTRIBUTING.md` says
  how.
- The two hook suites keep their original cases and gain one assertion each for
  the Gemini response shape, rather than running the whole matrix twice.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Comment thread .codex-plugin/plugin.json Outdated
Co-authored-by: Marcos Sevilla <31174242+marcossevilla@users.noreply.github.com>
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.

feat: add support for Codex

2 participants