Repository navigation
feat: run the gateway and workspace on OpenCode V2 - #1988
Conversation
… sync script Renovate bumps the version in opencode-v2-pin.json (major 2 only, no automerge). The per-asset digests do not follow, so scripts/sync-opencode-v2-pin.ts rewrites them from the release's SHA256SUMS; run it on the bump PR.
Add a V2 leg to the Workspace Image Smoke Test: build with the pinned version, check the plugins load with no network, and require /readyz to report the V2 version through the real entrypoint. Document the V2 pin, the build-arg switch, the paired deploy and the revert, and correct the V1-only statements in the living docs.
fro-bot
left a comment
There was a problem hiding this comment.
Verdict: CONDITIONAL
Blocking issues
- P2 — Reject unsupported arguments before synchronizing the pin.
scripts/sync-opencode-v2-pin.ts:90–105silently ignores unknown options. Both--helpand--check=trueenter write mode and exit successfully when digests differ. Thus an attempted inspection/check can replace the committed integrity pins. Validate the complete argument list before fetching or writing; reject unknown flags and surplus positionals, and make help side-effect-free if supported.
Non-blocking concerns
ci.yaml:710–715: addcurl --max-timeto the readiness probe. Ninety iterations do not bound a request that connects but never responds; that failure stalls diagnostics and cleanup until the job timeout.
Missing tests
- The new tests exercise pure helpers, but not CLI argument validation or check/write behavior. Add isolated cases for unsupported options, matching/stale
--checkwith byte-for-byte file preservation, explicit-version updates, and fetch failure without writes. - Focused empirical probe confirmed the argument issue:
--helpand--check=trueexited 0 and attempted writes; exact--checkexited 1 without writing. Fetch and writes were intercepted; repository files were unchanged. No build, lint, type-check, or test suite was run.
Probe command
node --input-type=module -e 'import {spawnSync} from "node:child_process"; const preload = `import fs from "node:fs"; import {syncBuiltinESMExports} from "node:module"; const pin = JSON.parse(fs.readFileSync("packages/runtime/src/shared/opencode-v2-pin.json", "utf8")); fs.writeFileSync = () => console.log("WRITE_ATTEMPT_INTERCEPTED"); syncBuiltinESMExports(); globalThis.fetch = async () => ({ok:true, text:async () => Object.keys(pin.assets).map(name => "b".repeat(64) + " " + name).join(String.fromCharCode(10))});`; for (const flag of ["--help", "--check=true", "--check"]) { const result = spawnSync(process.execPath, ["--import", "data:text/javascript;base64," + Buffer.from(preload).toString("base64"), "scripts/sync-opencode-v2-pin.ts", flag], {encoding:"utf8"}); console.log(JSON.stringify({flag,status:result.status,stdout:result.stdout,stderr:result.stderr})); }'Risk assessment (LOW): likelihood of regression, security exposure, or blast radius
V2 remains opt-in and the V1 default is unchanged. The confirmed defect affects local pin maintenance; downstream digest verification still fails closed. Provider routing and V2 egress/isolation remain deployment-validation gaps acknowledged by the PR.
Run Summary
| Field | Value |
|---|---|
| Event | pull_request |
| Repository | fro-bot/agent |
| Run ID | 40963666529 |
| Cache | hit |
| Session | ses_e7647155dffeCbIken7eDHf3zi |
| Scope | All 11 changed files; relevant consumers and tests |
| Delivery | Response-file review; inline posting unavailable |
| Review tooling | Seven specialist returns received; packaged return validation unavailable because its reference path was denied. Findings above independently inspected; blocking issue reproduced. |
… the pin The CLI ignored unknown options, so --help or --check=true ran a write-mode sync. Arguments are now validated up front: only --check and at most one x.y.z version are accepted, anything else exits 2 with a usage line before any read, fetch or write. The CLI takes injected I/O so its check, write and failure paths are tested. Also bound the V2 smoke's /readyz probe with curl --max-time 5.
| const defaultIo: SyncIo = { | ||
| fetch: async (url, init) => fetch(url, {...init, redirect: 'follow'}), | ||
| readPin: () => readFileSync(PIN_PATH, 'utf8'), | ||
| writePin: contents => writeFileSync(PIN_PATH, contents), |
fro-bot
left a comment
There was a problem hiding this comment.
Verdict: PASS
Commit b80469f resolves the prior blocking issue: unsupported arguments are rejected before pin or network I/O. The added tests cover argument rejection, check-only preservation, version updates, and upstream failures. The readiness probe now has a request timeout.
Blocking issues
None
Non-blocking concerns
None
Missing tests
None
Risk assessment (LOW): likelihood of regression, security exposure, or blast radius
The V1 default remains unchanged and V2 stays opt-in. No new correctness, security, or public-API regression identified. Provider routing and V2 egress/isolation remain acknowledged pre-deployment validation work.
Run Summary
| Field | Value |
|---|---|
| Event | pull_request |
| Repository | fro-bot/agent |
| Run ID | 40966893026 |
| Cache | hit |
| Session | ses_e7647155dffeCbIken7eDHf3zi |
| Scope | Re-reviewed b80469f against the previously reviewed head and prior findings |
| Probe | node scripts/sync-opencode-v2-pin.ts --help and node scripts/sync-opencode-v2-pin.ts --check=true, launched through a Node subprocess probe: both exit 2 with unsupported-option and usage diagnostics, confirming the original reproductions are rejected |
| Checks | Inspected new tests; no build, lint, type-check, or test-suite commands run |
This gets the gateway and workspace ready to switch to OpenCode V2 (#1805). The deployed default doesn't change: the workspace image still builds V1 unless it gets
OPENCODE_VERSION=<V2 pin>as a build-arg. Infra passes that build-arg once this ships in a release.Switch path
--build-arg OPENCODE_VERSION=2.2.0, the version inpackages/runtime/src/shared/opencode-v2-pin.json. The Dockerfile'sARGdefault stays at the harness V1 version, becauseharness-releaserewrites that line.opencodeVersionthe workspace reports on/readyz(refactor(gateway): add the OpenCode V2 run loop #1985).deploy/README.mdhas a new OpenCode V2 section covering the pin, the build-arg, the paired deploy, and the revert. The V1-only harness text is rewritten.CI
The Workspace Image Smoke Test now has a V2 leg alongside the unchanged V1 one, so the job name and the required checks stay the same.
v2-image-smoke.mjsruns as uid 10001 with--network none. Both plugins (systematicandfro-bot.opencode-plugin) load from the image's cache and reportactive.--network none./readyzmust return{"ready":true,"opencode":"ready","opencodeVersion":"<pin>"}. Readiness needs no provider or network, because the plugins install at build time.opencode --versionmust equal the pin, with no harness build.Renovate
A custom manager tracks
opencode-v2-pin.jsonagainstanomalyco/opencodereleases, limited to major 2, with no automerge. A bump changes onlyversion, soscripts/sync-opencode-v2-pin.tsrewrites the per-asset sha256 digests from upstreamSHA256SUMS, and--checkverifies them. The PR note asks the reviewer to run it. CI fails closed on stale digests.Docs
Updated the V2 facts in:
STRUCTURE.mdARCHITECTURE.mdpackages/gateway/AGENTS.mdapps/workspace-agent/AGENTS.mdversioned-toolskillA V2 workspace starts each OpenCode spawn with a fresh database, so it keeps no session history. The gateway already starts a new session on every mention.
Verification
ci.yamland ran it verbatim against a local arm64 V2 build. It exited 0, and the image was ready about 2 s after start with{"ready":true,"opencode":"ready","opencodeVersion":"2.2.0"}and both plugins active.check-types,lint,build(no change todist/),test:scripts(703 tests, including 8 new for the sync script and the Renovate pin invariants), anddeploy/scripts(146 tests) all pass.Not covered here: provider routing and the egress/isolation harnesses on V2. Those get checked against the V2 image before the infra switch, and again live after it.