cyberine
UsageRun recipes

The opencode2 worker (OpenCode 2.0 preview)

Retired 2026-09-06 (kzhyc, 402 Insufficient Balance) and the whole opencode family followed on 2026-09-07 (ks4sf). The section below describes the adapter as built; nothing here dispatches today.

opencode2-deepseek is a build-fleet worker driving the OpenCode 2.0 preview binary. It is a SIBLING of opencode-deepseek, not a replacement — v1 and v2 are separate binaries, and the v1 one is shared with other tooling, so both stay registered and can be enabled or retired independently. It is ranked below the v1 workers on purpose: v2 is a beta with no GA date and should not outrank a stable sibling on price alone.

Env vars: CYBERINE_OPENCODE2_DEEPSEEK_MODEL (default deepseek/deepseek-v4-pro), CYBERINE_OPENCODE2_DEEPSEEK_VARIANT, CYBERINE_OPENCODE2_MAX_RETRIES (default 2). The timeout knob is shared with v1 (CYBERINE_CLI_TIMEOUT_MS, deprecated alias OPENCODE_TIMEOUT_MS) rather than duplicated.

Retired 2026-09-06 (kzhyc) — DeepSeek account out of balance. opencode2 auth list still reports a valid DeepSeek credential (live 402 Insufficient Balance from api.deepseek.com on every dispatch), the same "valid key, unfunded account" shape RETIRED_WORKERS exists for — see "Retiring a worker" below. cyberine route no longer offers it; cyberine doctor reports it [WARN] opencode2-deepseek: RETIRED. Un-retire by deleting its RETIRED_WORKERS entry in src/core/workerProfiles.ts once the account is funded again.

Verified against v0.0.0-beta-17898 (2026-08-27). Four differences from v1, each checked against the real binary because a flag assumed from v1 fails silently:

v1v2
--variant <v>no such flag — the variant rides the model string as provider/model#variant
--dir <path>no such flag — the working tree comes from the spawn cwd alone
--auto and --dangerously-skip-permissions (both passed)--auto only
attaches to the shared background service--standalone, a private server per run

v2 shares v1's credential store: opencode2 auth list and opencode auth list report the same providers, and v2 ships no config directory of its own.

npm run selftest:cli-pwd-sync   # offline; the PWD contract this worker exposed
cyberine doctor                 # shows opencode2 install/login state

Resolved (ka8vw) — the sandbox now works. A sandboxed dispatch under the default HOST_SANDBOX_MODE=auto (Seatbelt on macOS) used to die with EPERM: operation not permitted, posix_spawn '<home>/.opencode/bin/opencode2'. The original suspicion — that the profile's cross-CLI credential deny (T640) overlapped the directory the binary lives in — turned out to be WRONG: cliConfigDirsFor('opencode2') already grants ~/.opencode write access (and therefore excludes it from the read-deny list), confirmed by rendering the actual profile. The real cause, confirmed with sandbox-exec -f bisection plus log show --predicate 'sender == "AppleSystemPolicy"': --standalone makes opencode2 fork its own server as a second, INNER process, and that inner spawn opens /dev/null for write to detach the forked server's stdio — denied by the profile's (deny file-write*) because /dev/null was never on the writable allow-list. Fixed by adding one narrow rule, (allow file-write-data (literal "/dev/null")), in generateSeatbeltProfile() (src/adapters/seatbeltProfile.ts) — granted unconditionally for every worker/backend since writing to /dev/null is inert (it discards data, exposes nothing). Guarded by npm run selftest:sandbox-devnull (scripts/ka8vw-sandbox-devnull-smoke.ts), positive-controlled: widening the rule past /dev/null (e.g. to (subpath "/dev")) flips the smoke red. HOST_SANDBOX_MODE=none is no longer required for this worker. An earlier failure in the same area — PlatformError: AlreadyExists: FileSystem.makeDirectory (~/.local/share/opencode/log), a blocked mkdir surfacing under a misleading errno — was fixed separately by giving opencode2 the same config dirs as v1 in cliConfigDirs.ts.

Known limitation — not enabled in fleet.json. The worker is registered in code but a fleet.json gates the candidate pool, so it will not be dispatched until it has an enabled entry there (cyberine doctor names it and suggests cyberine fleet init --merge). That is left to the operator on purpose: the v1 opencode-deepseek entry is deliberately disabled on this machine, so enabling its v2 sibling automatically would have contradicted an explicit choice.

Known limitation — usage on trivial turns. A single-turn answer that calls no tool emits no step_finish, so Usage comes back zero rather than small. Inherited from v1, not a v2 regression, but a cheap Q&A dispatch through this worker contributes nothing to the spend ledger.

Known limitation — no worker config is written. The v1 factory writes a flat opencode.json into the worker dir to stop an ancestor repo's config from overriding the model and permission flags. The v2 config schema has not been verified, and writing one from a guessed schema is worse than writing none, so the explicit -m and --auto on the argv are the only pins.