Skip to content

feat: run the gateway and workspace on OpenCode V2 - #1988

Merged
marcusrbrown merged 3 commits into
mainfrom
feat/opencode-v2-switch
Oct 30, 2026
Merged

marcusrbrown merged 3 commits into
mainfrom
feat/opencode-v2-switch

Conversation

@marcusrbrown

Copy link
Copy Markdown
Collaborator

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

  • The workspace image runs V2 when it's built with --build-arg OPENCODE_VERSION=2.2.0, the version in packages/runtime/src/shared/opencode-v2-pin.json. The Dockerfile's ARG default stays at the harness V1 version, because harness-release rewrites that line.
  • The gateway needs no config change. It picks V2 on each run from the opencodeVersion the workspace reports on /readyz (refactor(gateway): add the OpenCode V2 run loop #1985).
  • Deploy the gateway and workspace together. A gateway that's older than refactor(gateway): add the OpenCode V2 run loop #1985 can't run behind a V2 workspace. A new gateway behind a V1 workspace is fine.
  • Revert: redeploy the previous workspace and gateway images. Record both digests before you switch.

deploy/README.md has 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.

  • It builds the image with the pinned V2 version, reading it from the pin file rather than hard-coding it.
  • v2-image-smoke.mjs runs as uid 10001 with --network none. Both plugins (systematic and fro-bot.opencode-plugin) load from the image's cache and report active.
  • The real entrypoint starts with production security flags and --network none. /readyz must return {"ready":true,"opencode":"ready","opencodeVersion":"<pin>"}. Readiness needs no provider or network, because the plugins install at build time.
  • opencode --version must equal the pin, with no harness build.

Renovate

A custom manager tracks opencode-v2-pin.json against anomalyco/opencode releases, limited to major 2, with no automerge. A bump changes only version, so scripts/sync-opencode-v2-pin.ts rewrites the per-asset sha256 digests from upstream SHA256SUMS, and --check verifies them. The PR note asks the reviewer to run it. CI fails closed on stale digests.

Docs

Updated the V2 facts in:

  • STRUCTURE.md
  • ARCHITECTURE.md
  • packages/gateway/AGENTS.md
  • apps/workspace-agent/AGENTS.md
  • the versioned-tool skill

A 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

  • I extracted the new smoke step from ci.yaml and 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 to dist/), test:scripts (703 tests, including 8 new for the sync script and the Renovate pin invariants), and deploy/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.

… 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.
Comment thread scripts/sync-opencode-v2-pin.ts Fixed

@fro-bot fro-bot left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: CONDITIONAL

Blocking issues

  • P2 — Reject unsupported arguments before synchronizing the pin. scripts/sync-opencode-v2-pin.ts:90–105 silently ignores unknown options. Both --help and --check=true enter 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: add curl --max-time to 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 --check with byte-for-byte file preservation, explicit-version updates, and fetch failure without writes.
  • Focused empirical probe confirmed the argument issue: --help and --check=true exited 0 and attempted writes; exact --check exited 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 fro-bot left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

3 participants