fix(review): honour the configured adapter, and let review be pinned apart from the rest (AGT-4292) - #616
Merged
Merged
Conversation
…apart from the rest (AGT-4292) `openswarm review` read the config file for Linear settings and nothing else, so `runReviewer` fell through to the registry default — 'codex' — whatever the operator had configured. The daemon honours `adapter:`; the standalone CLI did not. Someone running `adapter: codex-responses` with an OpenRouter key had no way to review on openrouter. Review is also the one role worth pinning separately. It is the second opinion, so running it on the same provider as the work it checks is a correlated failure, and `reviewAdapter:` exists so that choice does not force every other role onto another provider. Precedence, most specific first: --adapter > OPENSWARM_REVIEW_ADAPTER > config.reviewAdapter > config.adapter > built-in default. An unknown name throws rather than falling through to a lower-precedence source, and names the valid options the way the registry's own error does — the operator asked for something specific and would otherwise silently get a different provider. A flag or the environment variable short-circuits before config is read. That is not an optimisation: `loadConfig` is not a pure read, it engages `enableHumanSurfaceReadOnly()` and the sandbox executor wiring, and a plain `openswarm review` never triggered either before this existed. When config IS consulted and the caller asked for JSON, `console` is silenced around the call, because `loadConfig` logs where it loaded from straight to stdout, in front of the JSON document. `ADAPTER_NAMES` is a leaf module rather than a re-export. Importing the adapter registry for a string check pulls in `codex.ts`'s module-scope `promisify(execFile)` and breaks eleven tests that mock `node:child_process`; putting it on `core/config.ts` breaks a test that mocks that module. Nothing in the source links the copy to the registry, so a test asserts they hold the same names — drift otherwise surfaces as a config that fails validation for an adapter the registry supports. Two things the review of this change found that it does NOT fix, both filed rather than folded in, because bundling an unrelated root cause into an adapter-resolution change is the mistake that cost three review rounds elsewhere today: AGT-4298 — `mcpClient.ts` calls `loadConfig()` unguarded during tool auto-discovery, so `review --json | jq` is still broken for any review that uses tools. Predates this change. Every test here stubs `deps.review`, so no test in the suite runs that path. AGT-4299 — `review --max` still resolves via `opts.adapter ?? activeAdapter()` and ignores `reviewAdapter:`, while `review --debug` reports the key as honoured. `reviewAdapter` is also documented nowhere. The stdout guard test was rewritten because it passed for the wrong reason: it spied on `process.stdout.write` while vitest intercepts `console.*` above that spy, so the pollution never reached its capture. It now captures both and asserts `JSON.parse` of the whole thing, which is what `jq` does. Review: layer 2 (independent subagent), two rounds. Round 1 found the stdout regression and the false-green guard; round 2 confirmed those fixed and found AGT-4298/4299 by running the real CLI. Mutants: removing the console suppression kills 2 tests, not propagating --json 2, removing the short-circuit 2, a phantom name in ADAPTER_NAMES 1. tsc --noEmit exit 0 · src/cli/ + src/core/ 1055 passed, exit 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
TL;DR
openswarm reviewread the config file for Linear settings and nothing else, sorunReviewerfell through to the registry default —codex— whatever the operator had configured. The daemon honoursadapter:; the standalone CLI did not. Someone runningadapter: codex-responseswith an OpenRouter key had no way to review on openrouter.Review is also the one role worth pinning separately: it is the second opinion, so running it on the same provider as the work it checks is a correlated failure.
reviewAdapter:exists so that choice does not force every other role onto another provider.Precedence
--adapter>OPENSWARM_REVIEW_ADAPTER>config.reviewAdapter>config.adapter> built-in defaultAn unknown name throws rather than falling through to a lower-precedence source, and lists the valid options the way the registry's own error does — the operator asked for something specific and would otherwise silently get a different provider.
A flag or the environment variable short-circuits before config is read. That is not an optimisation:
loadConfigis not a pure read — it engagesenableHumanSurfaceReadOnly()and the sandbox executor wiring, and a plainopenswarm reviewnever triggered either before this existed.Why
adapterNames.tsis a leaf moduleImporting the adapter registry for a string check pulls in
codex.ts's module-scopepromisify(execFile)and breaks eleven tests that mocknode:child_process; putting the constant oncore/config.tsbreaks a test that mocks that module. Nothing in the source links the copy to the registry, so a test asserts they hold the same names — drift otherwise surfaces as a config that fails validation for an adapter the registry supports.What the review found that this does NOT fix
Both filed rather than folded in, because bundling an unrelated root cause into an adapter-resolution change is the mistake that cost three review rounds on another PR today:
mcpClient.tscallsloadConfig()unguarded during tool auto-discovery, soreview --json | jqis still broken for any review that uses tools. Predates this change. Every test here stubsdeps.review, so no test in the suite runs that path.review --maxstill resolves viaopts.adapter ?? activeAdapter()and ignoresreviewAdapter:, whilereview --debugreports the key as honoured.reviewAdapteris also documented nowhere.This PR's claim is narrowed accordingly: adapter resolution no longer pollutes stdout, not "the
--jsoncontract is clean".A guard test that passed for the wrong reason
The test named "--json writes the verdict to stdout and keeps prose off it" spied on
process.stdout.writewhile vitest interceptsconsole.*above that spy — so the pollution this change briefly introduced never reached its capture array and the guard stayed green through it. It now captures both and assertsJSON.parseof the whole capture, which is whatjqdoes.Review
Layer 2 (independent subagent), two rounds. Round 1 found the stdout regression and the false-green guard; round 2 confirmed both fixed and found AGT-4298/4299 by building and running the real CLI. Layer 1 (
openswarm review) is not run on my own commits per standing policy.--jsonADAPTER_NAMESGate
tsc --noEmitexit 0 ·src/cli/+src/core/1055 passed, exit 0 ·oxlint0 warnings on the changed files.Closes AGT-4292.
🤖 Generated with Claude Code