Add Codex plugin integration - #418
Conversation
Port the existing Claude workflows to Codex-native skill, question, file, subagent, and background-session surfaces while preserving the standalone CLI contract. Keep the repository marketplace package aligned with Claude releases through the shared version updater.
a01cf64 to
ed834ca
Compare
Add portable root discovery and normalize distributed Agent Skills frontmatter while retaining current Codex and Claude packaging. Extend the version updater and tests so all identities remain synchronized.
umputun
left a comment
There was a problem hiding this comment.
two blocking things, then a pile of smaller ones.
make test can't run without uv
Makefile:16 puts uv run --with pyyaml python scripts/validate-portable-plugin.py as the first line of the test target. Make stops at the first failing line, so without uv on PATH the target exits 127 and go test never runs. Nothing in the project provides or documents uv: CI calls go test directly and never make test, the Dockerfile installs python3 py3-pip only so ghcr.io/umputun/ralphex-go has no uv, and CONTRIBUTING.md, CLAUDE.md, README.md and llms.txt don't mention it. So CI stays green and the breakage only shows up on a contributor's machine, at CONTRIBUTING step 3. --with pyyaml also makes make test need the network on a cold cache.
pls give the validator its own target and call that from CI instead of sitting in front of go test. Swapping uv for python3 isn't a fix on its own: import yaml is inside the per-skill try/except at scripts/validate-portable-plugin.py:46, so a python3 without pyyaml reports all four skills as invalid YAML: No module named 'yaml', which is worse than uv: command not found.
codex only allows request_user_input in Plan mode
ModeKind::allows_request_user_input() is true for Plan only, and default_mode_request_user_input, the feature that would enable it in Default mode, is under development and off. A call from ordinary Code mode comes back with request_user_input is unavailable in Default mode. Even force-enabled it wouldn't help: is_blocking is mode == Plan, so outside Plan mode the ask doesn't block and can't gate anything. Checked against codex 0.147.0.
every mandatory choice in the ported skills goes through that tool with no fallback: $ralphex steps 2-4, including the confirmation before starting an autonomous run that edits code for hours; ralphex-adopt lines 259, 269 and 280, which cover the never-silent-overwrite decision on an existing plan file and the accept/revise/reject gate; ralphex-update 103 and 132. The Claude versions are fine here because AskUserQuestion is always available, so this is introduced by the port. The one chat fallback in the port, ralphex-adopt:99, is scoped to "more than 4 possibilities", not to the tool being unavailable.
each of those needs an explicit fallback: if the tool errors or isn't available, ask the same question in chat, end the turn, wait for a real answer, and never assume a default.
smaller ones
- scripts/validate-portable-plugin.py:12 -
ROOT.glob("**/.codex-plugin/plugin.json")descends into dot dirs, so it also validates plugin copies under.ralphex/worktrees/and.claude/worktrees/. Both exist on disk in a working checkout. With a worktree open mid-edit on the plugin,make testin the clean main checkout fails naming paths from another branch.plugins/*/.codex-plugin/plugin.jsonis exact here. - scripts/validate-portable-plugin.py:46 - move
import yamlto module scope so a missing pyyaml surfaces as an ImportError naming the dependency, instead of four bogusinvalid YAMLerrors against valid frontmatter. Easy to hit since the shebang invites running the script directly. - llms.txt got no Codex section though README did. That's the file an LLM is pointed at for usage, so an agent reading it will tell a Codex user the integration doesn't exist. CLAUDE.md same story:
plugins/isn't in Project Structure, and Build Commands still callsmake testplain "run tests with coverage". - CLAUDE.md:511 - the plugin-version rule names only
assets/claude/, so a Codex-only skill fix triggers no version bump. Worth addingplugins/ralphex/skills/as a trigger and both new manifests as things to bump. - assets/claude/skills/ralphex/SKILL.md:3 -
argument-hintis gone but the skill still reads$ARGUMENTSat 29-31 and branches on "if no argument provided" at 46. The six-field frontmatter restriction applies to claude.ai uploads and the Skills API, not to plugin skills, and the new validator never readsassets/claude/, so dropping it loses the autocomplete hint for nothing. - plugins/ralphex/skills/ralphex-adopt/SKILL.md:246 -
--wrapdropped from the revdiff call vs the Claude source. It's a plain revdiff flag with no Claude coupling, and without it the approval view opens unwrapped on plans with long lines. - scripts/internal/README.md:8 says the script updates three version fields. It updates four: both Claude manifests plus
.codex-plugin/plugin.jsonand the portableplugin.json.
two design questions, not defects
- plugins/ralphex/skills/ralphex-adopt/SKILL.md:244 runs the target repo's own
scripts/launch-revdiff.shwhen it exists. That's repo-controlled code executing with workspace permissions during what the user authorized as a plan conversion. Narrow, since it needs that exact path present, but I'd have the installed launcher win. - all four
agents/openai.yamlsetallow_implicit_invocation: true, so codex can fire the skills without$ralphexbeing typed. The project already fights this from the other direction:codexTaskGuidancein pkg/processor/prompts.go is injected into every--codextask prompt because an auto-activated skill can override the task prompt. Was implicit invocation deliberate?
btw the verification list mentions quick_validate.py and validate_plugin.py, neither is in the repo or in the diff.
Codex question prompts are not reliably blocking outside Plan mode, so mandatory choices now fall back to chat without assuming defaults. Ralphex Adopt also trusts only a canonical installed RevDiff launcher and accepts only the documented approval result pairs. Keep Go tests independent of uv while validating the portable plugin explicitly in CI with pinned tooling. Tighten plugin discovery and align Codex/Claude documentation and invocation policy with the shipped workflows.
|
The Default-mode question behavior and the launcher exit contract were the important catches here: the original port could continue without a blocking answer, and it treated stdout as approval without first validating the launcher result. I pushed Mandatory choices in all four Codex skills now fall back to the same question in chat, end the turn, and never assume a default when the native question tool is unavailable, errors, or does not block. The skills are also user-invoked only.
The portable validator has its own I also restored the Claude Local checks passed: |
Review modes operate on the current checkout and may edit and commit findings, while stale progress files and option-like plan paths could make unsafe launches look valid. Require a clean committed feature-branch diff, block repository-local executable overrides, separate positional plans from flags, and confirm fresh process progress. Align Claude and Codex skill mode semantics and bump the independently versioned plugin payload.
Static manifest validation alone could not detect stale plugin versions, unsafe package traversal, or drift in launch-safety instructions, and the release hook only mutated an ephemeral checkout. Validate marketplace and agent metadata without following symlinks, add contract and negative tests, require a plugin version bump when distributed skills change, and keep CLI releases independent from plugin versioning.
umputun
left a comment
There was a problem hiding this comment.
the Codex plugin part looks fine, nothing turned up in plugins/ralphex/** itself. One part should come out of this PR though, and then a handful of fixes on what's left.
split out the launch preflight. 0b8f4aaa adds ~115 lines of Step 6/Step 7 fail-closed gating to assets/claude/skills/ralphex/SKILL.md and its Codex twin. Nothing in adding a Codex plugin needs it. The Step 2/3 rework is genuine integration work, the skill offered "Codex-only" wired to a deprecated flag and knew nothing about --codex, but the preflight is untrusted-repo hardening that would apply the same if this PR didn't exist. My round-1 comment was narrower than that, just ralphex-adopt running the target repo's own scripts/launch-revdiff.sh, and 088357b4 fixed exactly that.
What couples them now is scripts/validate-skill-contracts_test.py, which asserts the same contract strings in both copies, so reverting the Claude one breaks make test-plugin. That test came in ba1bab32, in this PR, so it isn't a constraint that existed before.
pls move both to a separate PR. It needs its own discussion, because as written the gates refuse things ralphex supports:
- blocking on an active
claude_command,codex_command,custom_review_scriptorvcs_commandin.ralphex/config, with no override, kills the skill for anyone who followed the docs.docs/custom-providers.mdheads seven config blocks with# in ~/.config/ralphex/config or .ralphex/config, anddocs/hg-support.md:32tells hg users to putvcs_commandthere. It also doesn't look at whether the selected run would use the key, so picking the Codex executor still fails on an unusedclaude_command. - the clean-tree requirement covers Review pipeline and External review, which is exactly what
modeRequiresBranch(cmd/ralphex/main.go:377) exempts from the uncommitted check, and whatpkg/git/service.go:212recommends as the way out of a dirty tree.ralphex --initleaves?? .ralphex/configbehind, so that alone blocks every review run. - the executable blocklist misses
notify_custom_script(pkg/config/values.go:728, executed atpkg/notify/custom.go:28), and it doesn't look at.ralphex/prompts/*.txtor.ralphex/agents/*.txtat all. Those are repo-local overrides of the prompts driving an agent with a shell, a wider surface than any single script path.
what's left needs these:
blocking: allowed-tools is now a space-separated string - assets/claude/skills/ralphex/SKILL.md:5, same in ralphex-adopt/SKILL.md:4 and ralphex-update/SKILL.md:4. Claude Code takes a comma-separated string or an array, whitespace isn't a separator, so Bash Read AskUserQuestion TaskOutput Glob resolves to one pattern matching no tool and the skills get an empty tool set. /ralphex then can't run which ralphex, can't launch, can't tail the progress file. Nothing here forces the change: the allowed-tools must be a string rule in scripts/validate-portable-plugin.py:255 only reads plugins/ralphex/skills/, and none of the four Codex skills declare the field.
blocking: version gate dies on an unreachable base - scripts/internal/check-plugin-version.py:108 raises on any base it can't rev-parse. CI feeds it github.event.before for push events (.github/workflows/ci.yml:37), and that commit is dangling after any rebase or amend force-push, while fetch-depth: 0 fetches refs, not dangling objects. The step runs ahead of build and test in the same job, so the Go suite, the wrapper tests and lint all go dark on a branch that touched no skill files. Treating an unresolvable base like the zeros base fixes it, pull_request still has a real base.sha.
on the step order itself: the round-1 uv problem was fixed by splitting make test-plugin out so it couldn't gate go test, and putting that step first in the only job puts the coupling back. Worth running it after build and test, or in its own job.
nits:
CLAUDE.md:513still says bump "on release" while.claude-plugin/README.md:21,scripts/internal/README.md:13and CI all say before merge. That file is what agents read as authoritative for this repo, so it will send them the wrong way.make bump-plugin-versionis also missing from the Build Commands block and fromCONTRIBUTING.md:20, so nothing a contributor runs locally exercises the gate.check-plugin-version.py:114only rejectscurrent == baseline, so a lower version passes as a bump. Takes a typo inmake bump-plugin-version VERSION=, but nothing else in the repo enforces ordering.scripts/validate-skill-contracts_test.pycovers 3 of the 8 shipped skill docs, and doesn't pin the Interactive Choice Contract from088357b4. Delete that paragraph from any of the four Codex skills andmake test-pluginstays green.validate-portable-plugin.py,validate-portable-plugin_test.pyandvalidate-skill-contracts_test.pyare maintainer-only but sit atscripts/root, whilecheck-plugin-version.pywent toscripts/internal/.docs/plans/completed/20260313-scripts-reorg.mdput internal tooling underinternal/and left root for user-facing things, andscripts/internal/README.mddocuments only the two that went there.update-plugin-version_test.sh, thejq_fixturecase runs the sed branch when jq is absent, so the suite reports both paths passing while only one ran.
Summary
Add a repository-owned Codex plugin that exposes the existing Ralphex workflows as
$ralphex,$ralphex-plan,$ralphex-adopt, and$ralphex-updatethrough the repository marketplace and/skillsdiscovery.The Codex skills preserve the Claude workflows' plan selection, approval gates, output files, failure behavior, and status flow while adapting interaction and background-process handling to Codex-supported surfaces. Mandatory choices fall back to chat and stop for a real answer when the native question tool is unavailable or non-blocking. Skills are explicitly invoked only, and
ralphex-adopttrusts only an installed revdiff launcher outside the target repository.This also hardens the shared
$ralphexlaunch contract. Claude and Codex users can select the configured executor or first-class--codexmode without confusing it with deprecated--codex-only/--external-onlysemantics. Review launches now fail closed unless the checkout is a clean non-default branch with a resolvable base and committed diff; repository-local executable overrides are rejected, positional plan paths are separated from flags, and launch confirmation requires both process state and fresh progress evidence.Portable-plugin validation has its own
make test-plugintarget and CI step, separate from the Go-onlymake test. It validates marketplace paths, fixed package-local skill discovery,agents/openai.yaml, explicit-invocation policy, and symlink containment. The plugin version is independent from the Ralphex CLI version: maintainers bump all four manifests explicitly when distributed skills change, and a base-aware CI gate enforces that invariant. GoReleaser no longer mutates plugin manifests in its temporary release checkout.Verification
Local repository checks passed on
ba1bab3:make test-plugin PLUGIN_VERSION_BASE=upstream/master— 10 portable-validator tests, 6 skill-contract tests, 8 version-gate tests, plus jq/sed updater fixturesmise exec go@1.26.5 -- make test— race-enabled Go suite, 87.8% coveragemise exec go@1.26.5 golangci-lint@2.11.4 -- make lint— 0 issuesgit diff --checkExternal Codex system validators, not files shipped by this repository, accepted all four skills and the plugin package earlier in the branch. The two Codex skills changed by the final safety pass,
$ralphexand$ralphex-adopt, were revalidated afterward.Hosted CI checks are not currently reported by GitHub, and a fresh installed-plugin Codex invocation remains unverified locally.