let the OpenCode harness run the pr-reviewer eligibility gate on a local model - #6113
Merged
Conversation
…cal model Stage 2 offered only Claude/Codex/Antigravity/Grok, so the one way to run a local Ollama model through it was a Claude binary pointed at an Anthropic-compatible shim — which also emits `[claude-code:unrecognized_model]` for every Ollama model id it is handed. OpenCode is the natural harness for Ollama and was ineligible. It had no vendor recipe because, unlike every other vendor here, it has no read-only argv flag: its tool posture, permission block and per-model `tool_call` advertisement all live in `OPENCODE_CONFIG_CONTENT`. So the recipe is two halves — `run --agent plan` from the vendor row, and `hardenOpencodeConfigForNoTool` applied to that config under the same `safetyProfile`, which denies every tool at the root and on every agent, marks each declared model `tool_call: false`, and clears MCP/plugins/share/autoupdate. The public-review env allowlist stripped the config outright, which did not harden the child — it pointed it back at the user's own ~/.config/opencode. It now survives on the same loopback terms as the local Anthropic credential, so a gateway config (which carries a cloud API key) still does not; the recipe is scoped to local namespaces to match. Existing spawn-time validation is unchanged and still applies: an Ollama-backed OpenCode wrapper must name an installed model whose /api/show reports no `tools` capability. Also collapses three copies of "local namespace, not a gateway" into `localRuntimeNamespace`, three copies of the OPENCODE_CONFIG_CONTENT parse into `parseOpencodeConfigContent`, and two hand-rolled loopback tests into the existing `isLocalInstanceEndpoint`.
…ort to the agent that runs Review findings on the previous commit: - An `ollamaBacked` provider whose stored config relocated the daemon off-box passed the marker-only eligibility check, but `cliChildEnv` then stripped its config — and a stripped config does not harden the child, it points OpenCode at the user's own ~/.config/opencode with tools, plugins and MCP servers intact, while the stage still reported an enforced tool-free gate. Both sides now gate on one `opencodeConfigIsLocalOnly` rule so the disagreement is unrepresentable. - The recipe offered MTPLX/llama.cpp/vLLM/SGLang wrappers that `validatePublicReviewModel` rejects unconditionally (only an Ollama catalog can be probed for the authoritative no-tools answer), i.e. a permanently blocking choice in the picker. Scoped to Ollama. - `buildAgentGeneration` writes reasoningEffort onto `agent.build`, but the stage runs `--agent plan`, so the configured thinking effort never reached the model. The harden step now seeds the review agent from `build`; an explicit user-declared `agent.plan` still wins. - Root `permission` is the string shorthand `deny` rather than three named categories, so a category OpenCode adds later cannot default to allowed. `parseOpencodeConfigContent` moves to providerModels.js, the leaf both the vendor row and the config builder can reach.
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
Stage 2 of the pr-reviewer pipeline (the tool-free eligibility gate) would not accept the OpenCode harness, so the only way to run a local Ollama model through it was a Claude binary pointed at an Anthropic-compatible shim — which also emits
[stderr] [claude-code:unrecognized_model]for every Ollama model id it is handed. OpenCode is the natural harness for Ollama; this makes it eligible.Why it had no recipe. Unlike every other vendor, OpenCode has no read-only argv flag — its tool posture, permission block and per-model
tool_calladvertisement all live inOPENCODE_CONFIG_CONTENT. So the recipe is two halves that must be read together:providerVendors.jsbuildsopencode run --agent plan -m ollama/<model>, not forwarding provider args (a saved--agent buildwould pick the tool-enabled agent);opencodeConfig.jshardens that config under the samesafetyProfile: rootpermission: "deny"(the string shorthand, so a category OpenCode adds later cannot default to allowed), an emptied tool map on every agent,tool_call: falseon every declared model, and MCP/plugins/share/autoupdate cleared.The public-review env allowlist used to strip that variable outright, which did not harden the child — it pointed OpenCode back at the user's own
~/.config/opencode. It now survives on the same terms as the local Anthropic credential.Eligibility and the allowlist share one locality rule (
opencodeConfigIsLocalOnly). They have to: a provider the vendor row accepts but whose config the allowlist strips would spawn with tools intact while every signal still reported an enforced gate. AnollamaBackedrecord with a relocated off-boxbaseURLis exactly that shape, and a marker-only check cannot see it. The recipe is scoped to Ollama becausevalidatePublicReviewModelrejects every other local runtime unconditionally — offering them would put a permanently blocking choice in the picker.Existing spawn-time validation is unchanged and still applies: the model must be installed and its
/api/showmust report notoolscapability.Also folded in (three duplications the change would otherwise have made worse):
localRuntimeNamespacereplaces three copies of "local namespace, not a gateway";parseOpencodeConfigContentreplaces three copies of theOPENCODE_CONFIG_CONTENTparse; and two hand-rolled loopback tests now use the existingisLocalInstanceEndpoint.Test plan
cd server && npm test— 38.6k tests green.providerVendors.publicReview.test.js— an Ollama-backed OpenCode wrapper is gate-eligible and enforced; the argv isrun --agent plan -m ollama/<model>with provider args dropped; a gateway wrapper, an off-boxbaseURL, and the four non-Ollama local runtimes are all withheld.opencodeConfig.test.js— hardening overrides a stored allow-everything config while preserving generation settings and the endpoint; the stage's effort reachesagent.plan; a user-declaredagent.planstill wins.cliChildEnv.test.js— the hardened config survives the allowlist whileGH_TOKEN/API_KEYdo not; an off-box config is stripped key and all; and the allowlist keeps the config for exactly the providers the vendor row makes eligible.gemini-3.8-flash); its four findings — the eligibility/allowlist disagreement, the unvalidatable runtimes, the effort never reaching--agent plan, and the root-permission wording — are each fixed and pinned by a test above.