From 55f935be64301c1194292116505c42bcd98c58f5 Mon Sep 17 00:00:00 2001 From: ayobamiseun Date: Thu, 9 Jul 2026 19:18:35 +0100 Subject: [PATCH 1/3] feat: add codex-reviewer subagent for programmatic reviews (#354) Slash commands do not compose, so downstream plugins that want to chain a Codex review into a pipeline had to dispatch the write-capable codex-rescue subagent and trust prompt-level instructions to keep it read-only, or shell into the plugin cache directly. Add codex:codex-reviewer, a thin forwarding subagent with the same shape as codex-rescue but hard-coded to review semantics: it forwards exactly one Bash call to codex-companion.mjs review (or adversarial-review when the request carries focus text or an adversarial framing), never adds --write, never forwards to task, has no resume semantics, and returns the companion stdout verbatim. Document the subagent in the README and pin its review-only contract in tests alongside the existing codex-rescue assertions. Fixes #354 --- README.md | 10 ++++++- plugins/codex/agents/codex-reviewer.md | 37 ++++++++++++++++++++++++++ tests/commands.test.mjs | 29 ++++++++++++++++++++ 3 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 plugins/codex/agents/codex-reviewer.md diff --git a/README.md b/README.md index 937a3037..a7f66392 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ If Codex is installed but not logged in yet, run: After install, you should see: - the slash commands listed below -- the `codex:codex-rescue` subagent in `/agents` +- the `codex:codex-rescue` and `codex:codex-reviewer` subagents in `/agents` One simple first run is: @@ -123,6 +123,14 @@ Examples: This command is read-only. It does not fix code. +### `codex:codex-reviewer` subagent + +Dispatches a read-only Codex review programmatically through the `Agent` tool - the review-mode counterpart to the `codex:codex-rescue` subagent. + +Slash commands do not compose, so other plugins and pipelines cannot invoke `/codex:review` or `/codex:adversarial-review` directly. The `codex:codex-reviewer` subagent fills that gap: one `Agent` call (`subagent_type: "codex:codex-reviewer"`) forwards the request to the plugin's `review` or `adversarial-review` runtime and returns Codex's output verbatim. + +It is hard-coded to be review-only: it never adds `--write`, never forwards to `task`, and has no `--resume` semantics. Requests with focus text or an adversarial framing route to `adversarial-review`; plain requests route to the built-in reviewer. + ### `/codex:rescue` Hands a task to Codex through the `codex:codex-rescue` subagent. diff --git a/plugins/codex/agents/codex-reviewer.md b/plugins/codex/agents/codex-reviewer.md new file mode 100644 index 00000000..f08afd45 --- /dev/null +++ b/plugins/codex/agents/codex-reviewer.md @@ -0,0 +1,37 @@ +--- +name: codex-reviewer +description: Use when Claude Code or another plugin needs to dispatch a read-only Codex code review programmatically - the review-mode counterpart to codex-rescue +model: sonnet +tools: Bash +--- + +You are a thin forwarding wrapper around the Codex companion review runtime. + +Your only job is to forward the review request to the Codex companion script. Do not do anything else. + +Selection guidance: + +- Use this subagent when a Codex code review of local git state should be dispatched through the `Agent` tool, for example from another plugin's pipeline or when the main Claude thread wants a Codex review without the interactive `/codex:review` flow. +- Do not use this subagent to investigate, fix, or implement anything. That is `codex-rescue` work. + +Forwarding rules: + +- Use exactly one `Bash` call to invoke `node "${CLAUDE_PLUGIN_ROOT}/scripts/codex-companion.mjs" review ...` or `node "${CLAUDE_PLUGIN_ROOT}/scripts/codex-companion.mjs" adversarial-review ...`. +- Choose `adversarial-review` when the request asks for an adversarial or challenge review, or includes extra focus text or custom review instructions. `review` maps to the built-in reviewer and does not support focus text. +- Otherwise choose `review`. +- Only pass through flags the review runtime accepts: `--wait`, `--background`, `--base `, `--scope `, `--json`, `--model `, and `--cwd `. Do not invent other flags. +- If the request did not explicitly choose `--background` or `--wait`, run in the foreground and wait for the review to finish. +- This subagent is review-only. Never add `--write`, and never forward to `task`. +- Do not call `task`, `setup`, `transfer`, `status`, `result`, or `cancel`. This subagent only forwards to `review` and `adversarial-review`. +- There are no `--resume`, `--fresh`, or `--resume-last` semantics here. Every review is a fresh run. +- Do not fix issues, apply patches, or suggest that you are about to make changes. +- Leave model unset by default. Only add `--model` when the user explicitly asks for a specific model. +- If the user asks for `spark`, map that to `--model gpt-5.3-codex-spark`. +- Preserve the user's focus text as-is apart from stripping the flags above. Do not weaken the adversarial framing or rewrite the user's focus text. +- Do not inspect the repository, read files, grep, monitor progress, poll status, fetch results, cancel jobs, summarize output, or do any follow-up work of your own. +- Return the stdout of the `codex-companion` command exactly as-is. +- If the Bash call fails or Codex cannot be invoked, return nothing. + +Response style: + +- Do not add commentary before or after the forwarded `codex-companion` output. diff --git a/tests/commands.test.mjs b/tests/commands.test.mjs index c34b0605..6506a743 100644 --- a/tests/commands.test.mjs +++ b/tests/commands.test.mjs @@ -170,6 +170,35 @@ test("rescue command absorbs continue semantics", () => { assert.match(readme, /### `\/codex:cancel`/); }); +test("codex-reviewer agent is a review-only thin forwarder", () => { + const agent = read("agents/codex-reviewer.md"); + const readme = fs.readFileSync(path.join(ROOT, "README.md"), "utf8"); + + assert.match(agent, /^name: codex-reviewer$/m); + assert.match(agent, /^tools: Bash$/m); + assert.match(agent, /review-mode counterpart to codex-rescue/i); + assert.match(agent, /thin forwarding wrapper/i); + assert.match(agent, /Use exactly one `Bash` call/i); + assert.match(agent, /codex-companion\.mjs" review \.\.\./); + assert.match(agent, /codex-companion\.mjs" adversarial-review \.\.\./); + assert.match(agent, /Choose `adversarial-review` when the request asks for an adversarial or challenge review, or includes extra focus text/i); + assert.match(agent, /does not support focus text/i); + assert.match(agent, /review-only\. Never add `--write`, and never forward to `task`/i); + assert.match(agent, /Do not call `task`, `setup`, `transfer`, `status`, `result`, or `cancel`/i); + assert.match(agent, /no `--resume`, `--fresh`, or `--resume-last` semantics/i); + assert.match(agent, /Every review is a fresh run/i); + assert.match(agent, /Do not fix issues, apply patches/i); + assert.match(agent, /Leave model unset by default/i); + assert.match(agent, /If the user asks for `spark`, map that to `--model gpt-5\.3-codex-spark`/i); + assert.match(agent, /Do not weaken the adversarial framing or rewrite the user's focus text/i); + assert.match(agent, /Do not inspect the repository, read files, grep, monitor progress, poll status, fetch results, cancel jobs, summarize output, or do any follow-up work of your own/i); + assert.match(agent, /Return the stdout of the `codex-companion` command exactly as-is/i); + assert.match(agent, /If the Bash call fails or Codex cannot be invoked, return nothing/i); + assert.match(readme, /`codex:codex-rescue` and `codex:codex-reviewer` subagents/i); + assert.match(readme, /### `codex:codex-reviewer` subagent/); + assert.match(readme, /subagent_type: "codex:codex-reviewer"/); +}); + test("transfer, result, and cancel commands are exposed as deterministic runtime entrypoints", () => { const transfer = read("commands/transfer.md"); const result = read("commands/result.md"); From dd70a99f370c4f25a7abea22da754d25175b9eb5 Mon Sep 17 00:00:00 2001 From: ayobamiseun Date: Thu, 9 Jul 2026 19:44:06 +0100 Subject: [PATCH 2/3] fix: detach --background reviews via Bash background mode The companion's review path parses --background but still calls runForegroundCommand unconditionally, so forwarding the flag alone would block the subagent until the review finishes. Mirror the slash commands: launch the single Bash call with run_in_background: true, skip BashOutput, and return the background status message instead of stdout. Foreground runs keep the verbatim-stdout contract. --- plugins/codex/agents/codex-reviewer.md | 6 ++++-- tests/commands.test.mjs | 8 +++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/plugins/codex/agents/codex-reviewer.md b/plugins/codex/agents/codex-reviewer.md index f08afd45..0f53a491 100644 --- a/plugins/codex/agents/codex-reviewer.md +++ b/plugins/codex/agents/codex-reviewer.md @@ -20,7 +20,9 @@ Forwarding rules: - Choose `adversarial-review` when the request asks for an adversarial or challenge review, or includes extra focus text or custom review instructions. `review` maps to the built-in reviewer and does not support focus text. - Otherwise choose `review`. - Only pass through flags the review runtime accepts: `--wait`, `--background`, `--base `, `--scope `, `--json`, `--model `, and `--cwd `. Do not invent other flags. -- If the request did not explicitly choose `--background` or `--wait`, run in the foreground and wait for the review to finish. +- The companion script parses `--wait` and `--background`, but Claude Code's `Bash(..., run_in_background: true)` is what actually detaches the run. Do not strip these flags yourself. +- If the request includes `--background`, launch the single `Bash` call with `run_in_background: true`. Do not call `BashOutput` or wait for completion. Instead of forwarding stdout, return exactly: "Codex review started in the background. Check `/codex:status` for progress." +- If the request did not explicitly choose `--background`, run the `Bash` call in the foreground and wait for the review to finish. - This subagent is review-only. Never add `--write`, and never forward to `task`. - Do not call `task`, `setup`, `transfer`, `status`, `result`, or `cancel`. This subagent only forwards to `review` and `adversarial-review`. - There are no `--resume`, `--fresh`, or `--resume-last` semantics here. Every review is a fresh run. @@ -29,7 +31,7 @@ Forwarding rules: - If the user asks for `spark`, map that to `--model gpt-5.3-codex-spark`. - Preserve the user's focus text as-is apart from stripping the flags above. Do not weaken the adversarial framing or rewrite the user's focus text. - Do not inspect the repository, read files, grep, monitor progress, poll status, fetch results, cancel jobs, summarize output, or do any follow-up work of your own. -- Return the stdout of the `codex-companion` command exactly as-is. +- For foreground runs, return the stdout of the `codex-companion` command exactly as-is. - If the Bash call fails or Codex cannot be invoked, return nothing. Response style: diff --git a/tests/commands.test.mjs b/tests/commands.test.mjs index 6506a743..b1531964 100644 --- a/tests/commands.test.mjs +++ b/tests/commands.test.mjs @@ -192,7 +192,13 @@ test("codex-reviewer agent is a review-only thin forwarder", () => { assert.match(agent, /If the user asks for `spark`, map that to `--model gpt-5\.3-codex-spark`/i); assert.match(agent, /Do not weaken the adversarial framing or rewrite the user's focus text/i); assert.match(agent, /Do not inspect the repository, read files, grep, monitor progress, poll status, fetch results, cancel jobs, summarize output, or do any follow-up work of your own/i); - assert.match(agent, /Return the stdout of the `codex-companion` command exactly as-is/i); + // The companion parses --background but still runs reviews in the + // foreground; only Claude Code's Bash background mode actually detaches. + assert.match(agent, /The companion script parses `--wait` and `--background`, but Claude Code's `Bash\(..., run_in_background: true\)` is what actually detaches the run/i); + assert.match(agent, /launch the single `Bash` call with `run_in_background: true`/i); + assert.match(agent, /Do not call `BashOutput`/); + assert.match(agent, /Codex review started in the background\. Check `\/codex:status` for progress\./); + assert.match(agent, /For foreground runs, return the stdout of the `codex-companion` command exactly as-is/i); assert.match(agent, /If the Bash call fails or Codex cannot be invoked, return nothing/i); assert.match(readme, /`codex:codex-rescue` and `codex:codex-reviewer` subagents/i); assert.match(readme, /### `codex:codex-reviewer` subagent/); From 8bb9a40faa56580332d8ebdee5cdb39509462096 Mon Sep 17 00:00:00 2001 From: ayobamiseun Date: Thu, 9 Jul 2026 20:07:03 +0100 Subject: [PATCH 3/3] fix: return a structured launch response for --background --json Background launches detach via Bash background mode, so the review's JSON payload does not exist when the subagent returns. Callers that combined --background with --json previously got the fixed prose launch message - non-JSON output despite requesting JSON. Return a fixed structured launch object for that combination and point callers at result --json for the completed payload. --- plugins/codex/agents/codex-reviewer.md | 1 + tests/commands.test.mjs | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/plugins/codex/agents/codex-reviewer.md b/plugins/codex/agents/codex-reviewer.md index 0f53a491..f5022321 100644 --- a/plugins/codex/agents/codex-reviewer.md +++ b/plugins/codex/agents/codex-reviewer.md @@ -22,6 +22,7 @@ Forwarding rules: - Only pass through flags the review runtime accepts: `--wait`, `--background`, `--base `, `--scope `, `--json`, `--model `, and `--cwd `. Do not invent other flags. - The companion script parses `--wait` and `--background`, but Claude Code's `Bash(..., run_in_background: true)` is what actually detaches the run. Do not strip these flags yourself. - If the request includes `--background`, launch the single `Bash` call with `run_in_background: true`. Do not call `BashOutput` or wait for completion. Instead of forwarding stdout, return exactly: "Codex review started in the background. Check `/codex:status` for progress." +- If the request includes both `--background` and `--json`, keep the launch itself identical but return exactly this JSON instead of the prose message: `{"status":"started","message":"Codex review started in the background. Check /codex:status for progress."}`. The review's own JSON payload is not available at launch time; callers fetch it afterwards with `/codex:result` or `result --json`. - If the request did not explicitly choose `--background`, run the `Bash` call in the foreground and wait for the review to finish. - This subagent is review-only. Never add `--write`, and never forward to `task`. - Do not call `task`, `setup`, `transfer`, `status`, `result`, or `cancel`. This subagent only forwards to `review` and `adversarial-review`. diff --git a/tests/commands.test.mjs b/tests/commands.test.mjs index b1531964..5adf6c6b 100644 --- a/tests/commands.test.mjs +++ b/tests/commands.test.mjs @@ -198,6 +198,11 @@ test("codex-reviewer agent is a review-only thin forwarder", () => { assert.match(agent, /launch the single `Bash` call with `run_in_background: true`/i); assert.match(agent, /Do not call `BashOutput`/); assert.match(agent, /Codex review started in the background\. Check `\/codex:status` for progress\./); + // --background --json callers still get machine-readable output: a fixed + // structured launch object, since the review payload does not exist yet. + assert.match(agent, /If the request includes both `--background` and `--json`/i); + assert.match(agent, /\{"status":"started","message":"Codex review started in the background\. Check \/codex:status for progress\."\}/); + assert.match(agent, /callers fetch it afterwards with `\/codex:result` or `result --json`/i); assert.match(agent, /For foreground runs, return the stdout of the `codex-companion` command exactly as-is/i); assert.match(agent, /If the Bash call fails or Codex cannot be invoked, return nothing/i); assert.match(readme, /`codex:codex-rescue` and `codex:codex-reviewer` subagents/i);