Recorded 2026-10-05. OpenCode is implemented as CodeConvoy's third independent backend. Claude was still planned at the time of this OpenCode validation; see the subsequent Claude validation. This record does not claim authenticated OpenCode E2E success. The OpenCode 2 compatibility section was added on 2026-10-20.
Recorded 2026-10-20 for CodeConvoy 1.3.0. A compatibility watch reported that OpenCode 2 may no longer accept --dir. That is confirmed: OpenCode 2 (tags v2.0.0, 2026-09-11, to v2.0.32, 2026-10-16) has no --dir for run, and its parser rejects unknown flags. Every OpenCode 2 job failed in that parser, and Check CLI already rejected OpenCode 2 for the missing flag. OpenCode 1 (opencode-ai on npm, latest 1.19.3) still supports --dir. OpenCode 2 is supported on macOS, Linux and Windows; on macOS and Linux CodeConvoy confirms that its detached server has stopped (see the cancellation decision below). OpenCode was not installed on the development Mac, so no installed-CLI or authenticated check was possible.
Sources:
- Migrate from V1: OpenCode 1 and 2 both use the
opencodecommand and are no longer installed side by side by default. OpenCode 2 is distributed as@opencode/cli(binsopencodeandopencode2), Homebrewanomalyco/tap/opencode-v2and the v2 installer; the v2 docs list these. - v2.0.32
runcommand: message,--standalone,--server,--continue,--session,--fork,--model("provider/model#variant"),--agent,--format default|json,--file,--title,--thinkingand--auto. There is no--dir,--variant,--attachor--port. - effect 4.0.1 CLI parser, used by OpenCode 2: an unrecognized flag is an
UnrecognizedOptionerror before the command runs; the lexer accepts--name=value. - v2.0.32 run implementation: the directory is
PWD, else the process working directory; piped stdin is merged into the message; setup errors are written as a JSONerrorevent with exit 1. - v2.0.32 server connection: without
--standaloneor--server,rundiscovers or starts a shared background service for the user account. Only the service's ownservice.jsoncan disable that, and CodeConvoy does not edit it. The CLI command docs useopencode run --standalonefor CI. - v2.0.32 standalone server:
serve --stdiois started as a child with the run's working directory and inherited environment, and ends when its stdin closes. The spawner detaches it into its own process group on macOS/Linux. - v2.0.32 non-interactive events: the same
step_start,text,tool_use,step_finishanderrorrecords, with finish reasonsstop,length,tool-calls,content-filter,errorandunknown. Errors set exit 1; an interrupt exits 130. A permission request is rejected with model-visible feedback and the model continues. - OpenCode 1 v1.19.3 run matches v1.18.34 apart from discontinued sharing: without
--dir, a local run uses the real path ofPWD, else the working directory. Its yargs setup is strict, so OpenCode 1 rejects--standalone.
Decisions:
- One argument list cannot serve both versions. OpenCode 2 requires
--standaloneto keep the server out of the shared service, and OpenCode 1 rejects that flag. OpenCode 2 also rejects--dirand--variant. - OpenCode 1's invocation is unchanged. Its
--diralso makes an OpenCode 2 binary fail before reading the task; likewise--standalonemakes OpenCode 1 fail. Neither runs in another directory, and CodeConvoy never retries with other arguments. - A new OpenCode setting, OpenCode version (
version:1default,2), selects the invocation. It is part of the immutable launch snapshot, so retries and reuse keep it, and existing saved state still means OpenCode 1.AgentBackend::buildcannot observe detection results, and recording the detected version automatically would need new preflight plumbing into the snapshot. Instead, a default trait methodcheck_detection_for(options, help)lets launch preflight verify the selected version againstrun --help. When the help matches the other version, the message names it and the setting. Check CLI and startup checks stay keyed by the executable, as before, and pass no version: they accept whichever supported interface the executable offers (review finding on #95). Other backends keep the default. - The OpenCode 2 help contract is the effect usage line
run [flags], JSON format, and--standalone,--format,--model,--agentand--autoas flag tokens. Text in a description, such as--print-logs' mention of--standalone, does not count. The version string stays informational. - OpenCode 2 runs
run --standalone --format jsonwith the process working directory set to the exact repository or isolated worktree. The backend requires an absolute path and removesPWDandGIT_*overrides as before. A Variant is sent as--model=provider/model#variant. A variant without a model, or a#in either value, is rejected because OpenCode 2 cannot express it. - The event shapes and finish reasons are the same. The real-CLI smoke test below showed that OpenCode 2 never prints the final step's
step_finish, so its completion rule differs (see that section). - Cancellation (review finding on #95): OpenCode's spawner starts the standalone server detached into its own process group on macOS and Linux.
runterminates and awaits that server only when it exits through its own lifecycle; CodeConvoy's cancellation, timeout and quit SIGKILL therunprocess group, which orphans the server until it notices its stdin lease closing. CodeConvoy could then release the repository while the server or its tools still write, which AGENTS.md forbids. #95 first refused OpenCode 2 on macOS and Linux. Its follow-up addsContainment::DetachedGroupsto the process layer, and OpenCode 2 commands use it with a 10-second grace. Whilerunis unreaped, CodeConvoy records the process groups its descendants move into. Stop, a time limit or quitting first send SIGTERM: effect'srunMaininterrupts the main fiber on SIGINT or SIGTERM, sorun's scope finalizer stops the server (SIGTERM, then SIGKILL after 3 s) and waits for it. SIGINT is not used, because a second SIGINT makesrunexit without its finalizer. After the usual group kill and reaping, the repository is released only once every recorded group is gone; recorded groups are probed with signal 0 and never signalled. A remaining group or an incomplete record keeps the repository quarantined. Windows is unchanged: CodeConvoy's Job Object (KILL_ON_JOB_CLOSE, no breakaway) contains the server and its tools, and cleanup waits until the job has no active process.
Fixtures: tests/fixtures/opencode-1.19.3-run-help.txt, opencode-2.0.32-run-help.txt, opencode-1.19.3-run.jsonl and opencode-2.0.32-run.jsonl are unmodified captures from the real-CLI smoke test below. The test agent behaves as OpenCode 2 when it is hard-linked under the name opencode2, which is also OpenCode 2's alternative bin name. In that mode it prints the captured v2 help to stdout and opencode v2.0.32, rejects flags outside the v2 run set, such as --dir, and, like the real CLI, prints no final step_finish. As OpenCode 1 it prints the captured help to stderr and rejects --standalone.
Checks on macOS (Darwin), Homebrew Rust/Cargo 1.99.1:
| Check | Result |
|---|---|
cargo fmt --check |
Passed |
cargo clippy --locked --all-targets --all-features -- -D warnings |
Passed (main including #96, which fixed an unrelated egui::WidgetType deprecation) |
cargo test --locked --all-features |
804 passed, 0 failed, 12 ignored, including the macOS/Linux detached-group tests (process level: cooperative stop, stubborn and orphaned servers, unchanged default; runner level: server gone at Cancelled, orphaned server keeps the repository reserved) |
cargo build --locked --release --bin codeconvoy |
Passed |
python3 -m unittest discover -s packaging -p 'test_*.py' |
110 tests passed, 1 skipped |
| Installed OpenCode 1 or 2 help/version probe | Not performed: OpenCode absent |
| Authenticated OpenCode 1 or 2 E2E | Unverified |
| Linux and Windows | Not run locally; left to the hosted CI matrix |
Official npm platform packages opencode-darwin-arm64@1.19.3 and @opencode/cli-darwin-arm64@2.0.32, verified against their npm sha512 integrity (Developer ID team 5NZ4Q7NXJ4), ran from a disposable /tmp sandbox on macOS arm64. HOME and XDG_* pointed into the sandbox, and auto-update, LSP downloads and the models fetch were disabled. There was no sign-in, no credential and no provider key. OpenCode's free default model answered anyway, so these were real, unauthenticated model runs with a harmless prompt. The repository was disposable and its path contained spaces and Unicode. The two versions need separate data directories: OpenCode 1 rejected OpenCode 2's database ("Database is not empty and has no session table").
| Check | OpenCode 1.19.3 | OpenCode 2.0.32 |
|---|---|---|
--version |
1.19.3 |
opencode v2.0.32 |
run --help |
Matches the contract, on stderr (stdout empty) | Matches the source-derived fixture, on stdout |
| Check CLI and launch preflight | Failed before this fix (help read from stdout); pass now | Pass (Windows; refused on macOS/Linux until #97) |
Generated arguments + --help (installed_opencode_accepts_generated_arguments) |
Pass after this fix | Pass, including --model=provider/model#high |
| Other version's argument | --standalone: exit 1 |
--dir: exit 1, Unrecognized flag: --dir |
| Read-tool task, JSON events | step_start, tool_use, step_finish:tool-calls, step_start, text, step_finish:stop, exit 0 |
step_start, tool_use, step_finish:tool-calls, step_start, text, exit 0, no final step_finish (three runs) |
| Job through CodeConvoy's runner | Succeeded after this fix (Activity shows the tool path) | Reported Failed before this fix |
| Tool input key | filePath (absolute) |
path (relative), now shown in Activity |
Standalone server (observed with ps) |
n/a | serve --stdio in its own process group; SIGTERM to run exited with 130 after 50 ms, server and its git children gone |
The OpenCode 2 source explains the missing event. After submitting the prompt, run waits for the session to become idle, reconciles the final messages and closes its event stream without reading the last session.step.ended. Every failure path sets exit 1 (failed step or execution, interrupt not caused by the user, cancelled question, unpromoted prompt, message error), and a user interrupt exits with 130. So OpenCode 2's completion rule is: exit 0, a final step that started and produced its own response (text or tool_use; earlier steps' answers do not count), no error event, valid bounded JSON, and no final step that ended unfinished or with a non-stop reason. A token-limit stop on the final step cannot be detected because OpenCode 2 does not report it. OpenCode 1 keeps its rule (final step_finish reason stop). Detection now reads OpenCode's help from both streams, and so does the image-attachment check.
Other OpenCode 2 changes, reported but not in this fix's scope: a new plugin API, server API and client contracts; TUI configuration moved to cli.json; and external_directory allowed by default in OpenCode 2 (OpenCode 1 asks). In non-interactive runs, permission asks and questions are rejected or cancelled with feedback instead of interrupting the session. Session continuation (--continue/--session) is unused by CodeConvoy.
Authoritative references inspected before implementation:
- CLI documentation:
run, JSON format, model/agent/variant options,--auto, help/version and discovery commands. Model discovery is available throughopencode models; CodeConvoy uses a default or free-form qualified model override instead of maintaining a catalog. - Permission documentation: allow/ask/deny rules, explicit-deny preservation in auto mode, permissive defaults and agent-specific overrides. These are tool permissions, not an operating-system sandbox.
- Agent documentation: primary agents and subagents. CodeConvoy leaves agent definitions in OpenCode.
- Released v1.18.34 run implementation: piped stdin via
Bun.stdin.text()andresolveRunInput, local in-process server when not attaching, directory handling usingPWD, automatic rejection of approval requests without auto mode, JSON event framing, error exit paths and agent fallback warnings. - Released v1.18.34 processor: finish reason carried in step-finish parts.
- The development source at
a2aea963d1fb340396e66626c9e9d730f5bfee81was also compared. The features used here agree with the released source and documentation. Local installed-version differences cannot be assessed because OpenCode is absent.
Exact non-secret argument shape (brackets indicate optional arguments, not literal arguments):
OpenCode 1 (default):
<executable> run --format json --dir <absolute-repository-path>
[--model=<provider/model>] [--agent=<primary-agent>] [--variant=<variant>] [--auto]
OpenCode 2 (working directory = absolute repository or worktree path):
<executable> run --standalone --format json
[--model=<provider/model>[#<variant>]] [--agent=<primary-agent>] [--auto]
The prompt is sent as exact stdin bytes followed by EOF, never interpolated into a shell or placed on the command line. Cwd is the selected repository. Native path arguments are retained; inherited PWD and GIT_* overrides are removed. Detection uses <executable> run --help and <executable> --version, without a task. Version text is displayed, not converted into a guessed semantic-version compatibility rule.
CodeConvoy never requests, stores or edits provider credentials. It inherits OpenCode's provider, authentication and configuration environment. No provider manager, keys UI, custom model API, Claude backend, Activity normalization, scheduler feature or release automation was added. OpenCode's own plugins, hooks, MCP processes, session persistence and configured automatic sharing remain its responsibility.
- Keep
AgentBackendandAgentOutputunchanged. The existing option metadata, command specification and per-job decoder already allow a third backend with independent semantics. Codex and Copilot implementations are unchanged. - Expose executable, optional model, primary agent, provider/model variant and permission mode. Blank model/agent/variant means OpenCode default. No fixed models or universal reasoning controls.
--thinkingcontrols display rather than reasoning effort and is not exposed. - Default permission mode adds no override. OpenCode's existing allowed operations can execute; requests requiring approval are rejected. Explicit auto mode adds
--auto, retaining configured denies. Neither mode provides filesystem/process isolation. - Use JSON for a real completion check, not merely display. Exit zero plus a final
step_finishwith reasonstop, no session error and no malformed/oversized JSON record is required. Starting a later step clears the previous stop. Unknown events and complete tool records remain visible; assistant text is rendered directly. Stderr and plain diagnostics remain visible. Tool failures alone can be recoverable and do not prove task failure or success. - Reuse shared process-group/Windows Job Object cancellation. No attach/server reuse or daemon is launched by CodeConvoy. Confirmed termination releases the repository and both capacity slots. Unconfirmed cleanup uses the existing conservative quarantine. A Unix descendant that deliberately detaches cannot be guaranteed contained.
- Persist the additive
opencodeagent ID and its non-secret options in the existing version-1 schema. Defaults are materialized by preflight; snapshots are immutable. Accepted launch clears only prompt/selection. Reuse restores task/settings/registered selection without starting jobs. Existing Codex/Copilot state loads unchanged; older binaries cannot read a newly saved OpenCode variant.
Environment: macOS (Darwin), Rust/Cargo 1.99.0, project minimum Rust 1.95. OpenCode was not on PATH. It was not installed. No provider credentials were requested and no authenticated agent task was run.
| Check | Result |
|---|---|
cargo fmt --check |
Passed |
cargo clippy --all-targets --all-features -- -D warnings |
Passed |
cargo test --all-features |
62 passed, 0 failed; 2 installed-CLI probes intentionally ignored |
cargo build --locked --release |
Passed (optimized macOS binary) |
| Native macOS UI | Launched the actual ui::App using temporary state; OpenCode controls, missing-CLI error, Codex/Copilot switching, minimum 780×560 viewport and larger resized window inspected; dark and light rendering checked |
| Headless egui layout | All three implemented backends fit 310/360/520-point editor widths in both themes; detection remains tied to selected agent/settings |
| Linux launch / Linux desktop UI | Not performed: this session runs on macOS with no configured Linux execution environment |
| Real OpenCode help/version/generated-argument probe | Not performed: CLI absent; optional help-only test is provided |
| Authenticated OpenCode E2E | Unverified |
| Hosted CI matrix | Not run in this session; workflow unchanged, local equivalent checks passed |
The temporary UI launcher and app wrapper are not shipped. No production app state was used for that check.
Coverage includes missing and incompatible executables, help capabilities and displayed version, default/optional arguments, literal stdin and actual repository cwd, permission mapping, invalid settings, split UTF-8, tool/error/unknown events, final-stop requirements, malformed/oversized records, nonzero and signal exits, descendant cancellation and unaffected peers, stale Git baselines, version-1 round trips, backend preferences, snapshotting, draft clearing, reuse, mixed-backend concurrency, multiple OpenCode convoys and canonical repository-lock release after success/failure/cancellation. Existing Codex/Copilot command regression assertions remain in place.
The help fixtures used by this original record were synthetic; captured help from OpenCode 1.19.3 and 2.0.32 replaced them on 2026-10-20. The executable in tests/support/agent.rs is a deterministic test process using disposable repositories and handshake gates. Its success and error events match the documented source protocol but are not evidence of provider execution.
- Authentication, provider/model availability, platform-specific OpenCode behavior and actual CLI flag acceptance need installed-CLI and authenticated tests. Help checks verify exposed capabilities, not every runtime semantic. Older CLIs lacking any required option are rejected even when that particular optional setting is blank.
- OpenCode can fall back to its default agent on an unknown or subagent selection. Its warning stays visible. No provider entitlement or full agent-registry validation is attempted by CodeConvoy.
- Existing permissions can allow shell and files; auto mode can broaden asked permissions. Neither is an OS sandbox. There are no synthetic “read-only” or “file edits only” guarantees.
- A 256 KiB stdout record exceeds the decoder's bounded protocol verification and fails conservatively, even if the process later exits zero. CLI completion, including a valid stop, is not proof of task fulfillment. Always inspect response and diff.
- OpenCode can retain its own sessions or auto-share according to its existing configuration. CodeConvoy does not rewrite that configuration or duplicate its management UI.
- Linux desktop and real provider E2E remain outstanding. Codex/Copilot's earlier user-verified Linux E2E status is preserved, not re-claimed as a new run here.
- OpenCode 2 support is source- and fixture-verified only; no OpenCode 2 binary was run. Its standalone-server stand-in (
tests/support/standalone.rs) models the source behavior: a server in its own process group thatrunstops on exit and on SIGTERM. Users must select OpenCode version themselves. Check CLI detects a mismatch but does not change the setting, and saved settings default to OpenCode 1. Retry reuses the original setting, so after a major upgrade start a new or reused convoy with the new version selected. - On macOS and Linux, OpenCode 1's shell-tool commands run in their own process groups, outside CodeConvoy's process-group cancellation; CodeConvoy cannot confirm that they ended (OpenCode 1 does not opt into detached-group tracking). For OpenCode 2, a helper orphaned by
run's exit before the next 200 ms sample is still found by the exit sweep when its working directory is inside the job's directory, as OpenCode's server and tools are; a helper elsewhere, or one that daemonizes into a new group mid-job by itself, is not recorded. A command of this user that starts inside that directory during the final sampling interval is treated as possibly the job's, which keeps the repository reserved until it ends. If OpenCode cannot stop its server within the grace period, the job's repository stays reserved until the processes end and CodeConvoy restarts. Windows Job Objects contain both versions. - OpenCode 2 cannot express a variant without a model. CodeConvoy rejects that combination instead of choosing a model.
Use the safe two-repository procedure in README. On Linux, also check all three selections and Check CLI, resize to 780×560 and larger sizes, confirm scroll access to every field and fixed execution controls, and inspect light/dark/system themes. Record the real OpenCode version, selected primary agent/model and outcome without credentials before marking OpenCode E2E verified.