fix(review): replace built-in plan agent with custom review agent#49
Merged
JohnnyVicious merged 1 commit intomainfrom Apr 12, 2026
Merged
fix(review): replace built-in plan agent with custom review agent#49JohnnyVicious merged 1 commit intomainfrom
JohnnyVicious merged 1 commit intomainfrom
Conversation
OpenCode's built-in `plan` agent isn't just read-only — it injects a
synthetic user-message directive on every turn ("Plan mode ACTIVE —
STRICTLY FORBIDDEN... produce an implementation plan") which overrides
whatever system prompt the caller sends and makes the model return an
implementation plan instead of the requested review. This has been the
root cause of `/opencode:review` and `/opencode:adversarial-review`
returning implementation plans since the plugin was first ported from
codex-plugin-cc, where the equivalent was `sandbox: "read-only"` — a
syscall-level filesystem sandbox that doesn't touch the system prompt.
This commit ships a dedicated `review` agent inside the plugin and
threads OPENCODE_CONFIG_DIR through `ensureServer` so OpenCode discovers
it when the companion spawns the server. The agent is read-only at the
permission layer (deny `*`, allow only read/grep/glob/list) and has a
neutral prompt body that tells the model to follow the caller's brief
exactly without inventing implementation steps.
Both review handlers and the stop-review-gate hook now resolve the
agent through a shared `resolveReviewAgent` helper. When the custom
agent isn't available (user already had `opencode serve` running
without our config dir), the helper falls back to `build` + per-call
`tools: {write:false, edit:false, ...}` overrides with a warning log,
so reviews still work correctly even if the preferred path isn't taken.
Also fixes a latent bug surfaced during end-to-end testing: the CLI
passed `--model` as a plain string through to OpenCode's HTTP API,
which expects `{providerID, modelID}` and rejected every invocation
with HTTP 400. A new `parseModelString` helper splits on the first
slash (preserving nested model ids like `openrouter/anthropic/...`)
and callers convert at the boundary.
Tests:
- 10 unit tests for `resolveReviewAgent` covering array/object list
shapes, missing agent fallback, listAgents errors, and tool mutation
safety
- 9 unit tests for `parseModelString` covering provider splitting,
trimming, empty/invalid input, and non-string rejection
- End-to-end verified by running the companion script against this
branch's own working tree with `--model openrouter/anthropic/claude-haiku-4.5`:
server spawned with OPENCODE_CONFIG_DIR, custom agent was resolved
(job log shows `agent: review`), adversarial review returned with a
structured needs-attention verdict + two findings (review-shaped, not
plan-shaped)
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.
Summary
reviewOpenCode agent inside the plugin (atplugins/opencode/opencode-config/agent/review.md) and threadsOPENCODE_CONFIG_DIRthroughensureServerso OpenCode discovers it when the companion spawns the server./opencode:reviewand/opencode:adversarial-review(and the stop-review-gate hook) now route through the new agent via a sharedresolveReviewAgenthelper, with a fallback tobuild+ per-calltoolsrestrictions when the custom agent isn't available on a pre-running server.--modelwas passed as a plain string but OpenCode's HTTP API expects{providerID, modelID}, so every--modelinvocation was silently erroring out with HTTP 400. A newparseModelStringhelper converts at the CLI boundary.Why
OpenCode's built-in
planagent isn't just read-only — it injects a synthetic user-message directive ("Plan mode ACTIVE — STRICTLY FORBIDDEN… produce an implementation plan") on every turn, which dominates whatever system prompt the plugin sends. The result: users running/opencode:reviewor/opencode:adversarial-reviewgot back an implementation plan instead of the requested review. This has been broken since the plugin was first ported from codex-plugin-cc, where the equivalent call used `sandbox: "read-only"` — a syscall-level filesystem sandbox with no prompt-layer effects. The opencode port translated that to `agent: "plan"` under the assumption that plan was the read-only counterpart to build, but missed the prompt-layer side effect.A custom review agent sidesteps the injection entirely (
insertReminders()in OpenCode only fires onagent.name === \"plan\"), while still enforcing read-only behavior at the permission layer.Test plan
Known limitation
If a user already has `opencode serve` running in another terminal when the plugin runs, `ensureServer` reuses the existing process and `OPENCODE_CONFIG_DIR` is never set, so the custom agent isn't loaded. The fallback catches this via `listAgents()`, uses `build` with per-call tool overrides, and logs a warning telling the user to stop the pre-existing server. Correctness is preserved; only the prompt layer is slightly less clean than the preferred path.