Skip to content

let the OpenCode harness run the pr-reviewer eligibility gate on a local model - #6113

Merged
atomantic merged 2 commits into
mainfrom
cos/task-mtlvx3zg/agent-7457e872
Sep 3, 2026
Merged

let the OpenCode harness run the pr-reviewer eligibility gate on a local model#6113
atomantic merged 2 commits into
mainfrom
cos/task-mtlvx3zg/agent-7457e872

Conversation

@atomantic

Copy link
Copy Markdown
Owner

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_call advertisement all live in OPENCODE_CONFIG_CONTENT. So the recipe is two halves that must be read together:

  • providerVendors.js builds opencode run --agent plan -m ollama/<model>, not forwarding provider args (a saved --agent build would pick the tool-enabled agent);
  • opencodeConfig.js hardens that config under the same safetyProfile: root permission: "deny" (the string shorthand, so a category OpenCode adds later cannot default to allowed), an emptied tool map on every agent, tool_call: false on 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. An ollamaBacked record with a relocated off-box baseURL is exactly that shape, and a marker-only check cannot see it. The recipe is scoped to Ollama because validatePublicReviewModel rejects 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/show must report no tools capability.

Also folded in (three duplications the change would otherwise have made worse): localRuntimeNamespace replaces three copies of "local namespace, not a gateway"; parseOpencodeConfigContent replaces three copies of the OPENCODE_CONFIG_CONTENT parse; and two hand-rolled loopback tests now use the existing isLocalInstanceEndpoint.

Test plan

  • cd server && npm test — 38.6k tests green.
  • New coverage pins each enforcement half and the seam between them:
    • providerVendors.publicReview.test.js — an Ollama-backed OpenCode wrapper is gate-eligible and enforced; the argv is run --agent plan -m ollama/<model> with provider args dropped; a gateway wrapper, an off-box baseURL, 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 reaches agent.plan; a user-declared agent.plan still wins.
    • cliChildEnv.test.js — the hardened config survives the allowlist while GH_TOKEN/API_KEY do 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.
  • Reviewed by antigravity (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.
  • Follow-up filed as Pin the invariant that a public-review stage never dispatches through the CoS runner #6105 (pin the "public-review never dispatches through the CoS runner" invariant; needs mock scaffolding beyond this diff).

…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.
@atomantic
atomantic merged commit 637811b into main Sep 3, 2026
7 checks passed
@atomantic
atomantic deleted the cos/task-mtlvx3zg/agent-7457e872 branch September 3, 2026 19:52
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.

1 participant