OpenCode vs Claude Code: Which to Pick, With 50 Logged Tasks
A feature table from the official docs, a pick-this-if list, and what 50 logged OpenCode tasks did next to my 20 Claude Code ones.

If you are choosing between OpenCode vs Claude Code, the short answer is this: pick OpenCode if you want one CLI that runs models from many providers, and pick Claude Code if you want one vendor's models and its settings system. I run both as headless workers, giving the hard tasks to Claude Code and the trivial ones to OpenCode's free models. Below are a feature table from the official docs and what 50 logged OpenCode tasks showed.
Limits, once: the tasks were not matched pairs, and every number here is a count, not a rate.
Updated 2026-10-30. Sources: the OpenCode docs (intro, CLI, providers, permissions, config, MCP) and the Claude Code docs (overview, headless, permissions, settings, MCP).
Pick OpenCode if... / Pick Claude Code if...
Pick OpenCode if:
- You want to choose the provider and model on every call. Its docs say "you can use any LLM provider by configuring their API keys", list "75+ LLM providers" and local models, and
--modeltakes the form provider/model. - You want an open source agent. The docs call it "an open source AI coding agent".
- You want a fallback list when a free model is rate limited. In my log, three free-model tasks were rate limited at the first call and each ran fine on the next free model (details below).
Pick Claude Code if:
- You want one vendor's models behind one account. The docs say most surfaces need a Claude subscription or an Anthropic Console account.
- You want documented scripting behaviour:
claude -p"exits with code 0 on success and a non-zero code when the run fails", plus--allowedToolsto pre-approve tools. - You want its settings files to carry the team's permission rules. In my 20 Sonnet tasks, 15 delivered and 5 did not, covered in Claude Code vs Cursor.
Feature comparison from the official docs
| OpenCode | Claude Code | |
|---|---|---|
| Model providers | "75+ LLM providers", plus local models | Claude models; the terminal CLI also supports third-party providers |
| Non-interactive command | opencode run [message..], with --model and --format json | claude -p "<prompt>", with --output-format json |
| Permission control | allow, ask or deny per tool under permission; --auto approves anything not explicitly denied | allow, ask and deny rules in settings; --allowedTools and --permission-mode on the command line |
| Config file | opencode.json (JSON or JSONC) | settings.json in ~/.claude/ and .claude/ |
| MCP support | Yes, defined under mcp in the config | Yes, an "open source standard" it connects through |
| Open source / license | Called "open source"; license not stated in docs | Not stated in docs |
| How you pay | Your own API keys; Zen (add billing details); Go, "a low cost subscription plan"; free tier not stated in docs | Claude subscription or Anthropic Console account; third-party providers for the terminal CLI |
Two commands show the whole difference in one flag:
opencode run --model <provider>/<model> "<prompt>"
claude -p "<prompt>" --allowedTools "Read,Edit,Bash"
With OpenCode I choose the model on every call. With Claude Code the docs show /model sonnet as a value you can pass in a -p run, and in my setup I pin the alias sonnet, which tracks the latest Sonnet.
What 50 logged OpenCode tasks showed
Each logged task is one dispatched job, and some cover several runs. The 50 tasks run from 2026-09-26 to 2026-10-01 and use three models. The Claude tasks span 2026-10-01 to 2026-10-04, so the two sets barely overlap in time. A task records what I saw when I wrote it, including reviews that were still pending.
| Model group | Tasks | Result in the log |
|---|---|---|
Free model A (space-bunny-free) | 34 | 24 with no non-zero exit, interruption or overrun recorded; 10 with at least one of those: 1 ended with no commit; in the other 9 the work was finished, though some record a later review still pending |
Free model B (muse-spark-1.3-contributor-free) | 3 | 3 rate-limited within a minute, no result; each fell through to free model A |
Paid-plan model (muse-spark-1.3-contributor) | 13 | 11 finished with exit 0; 1 stalled (exit 125); 1 has no outcome recorded |
Free models: three rate limits at the first call
One day I sent three tasks to free model B, the first model on my free list. All three came back in under a minute with no result: "Rate limit exceeded" at start for a release-audit re-stamp, "rate limited again (2/2 today)" for a read-only investigation, and a rate limit at start for a release-notes module. The investigation was logged as stalled, not as a bad answer.
I sent each brief to the next model on the list, free model A, and all three tasks were accepted. The rule I took from it: a rate limit means try the next model, and it is a quota event, not a quality verdict. That is why a free-model fallback list works at all, and it means the thing I am measuring is the list, not any single model.
The cost is that I have to maintain that list. The free-first rule in my log exists because 26% of the paid plan's monthly quota went in the first three days, so I moved routine work to free models. The rate-limit fallback post covers the full list.
An exit code of 1 does not mean the work is lost
Four free-model-A tasks record a non-zero exit on a run whose work was complete. One was exit 1 after 765 seconds on a spec amendment, with a single commit that I accepted as-is; the error was a late "Invalid arguments for tool edit". Another was exit 125 ("socket closed") after the work was done, on the release-notes module; I accepted it after review, and a Codex review then found four real design gaps in it. The other two were exit 1 with finished work.
Because of those, my log carries this lesson: exit 1 is not "no result", so check git before re-dispatching. The retry changes too. One free-model-A task on an 11-file change hit my 2,700-second hard cap with exit 124. The code was complete, formatting and static checks were clean and 930 tests passed, but lint was slow on a busy machine and nothing was committed. Starting over would have redone the whole task. I sent the same model a finish prompt that asked only for lint and a commit. It exited 0 in 120 seconds, and my review of the diff found nothing to fix.
Claude Code's docs say a non-zero exit means the run failed, and in my 20 Sonnet tasks a hung test and an uncommitted-edits exit appear, so neither tool gets a pass. With a many-model CLI, the exit code tells you about the provider as well as the task.
The run that copied the kit
One task took two runs and ended with no commit. It was on a shared UI kit: a document tile and a span change. The first run ended with exit 125 (a stall) during a long silence in verification and lint. The code was written and 471 unit tests had passed, but nothing was committed. A build daemon from the killed run kept building after the kill.
In the finish run the model invented a scratch directory containing a timeout shim and a full copy of the kit, recreated it after it was deleted, and ran Gradle there while the machine's load sat between 32 and 40. It was killed at 55 minutes with no commit.
The fix I wrote down was to my briefs, not to the model choice: forbid copying the repo elsewhere, and name the exact timeout binary. I have no later task that tests those rules on the same job.
Pin the model, or the run is not about the model
My log has one earlier incident, from before this table. A run with no --model flag took OpenCode's persisted default, and looped for about 15 minutes on one Swift compile error (using self before all stored properties were initialised), re-reading the same 40 lines and reporting itself "confused by stale reads". It looked like the worker performing badly. When I checked which model had run, it was whatever default OpenCode had stored, not one I had chosen, and it was too small for a language-rule error like that.
After that my rule was to pass the model on every call. Every one of the 50 logged tasks names its model, and none records that kind of loop. My log's rule is never to judge OpenCode's competence from a run where the model was not pinned. This is the cost of "any provider": the CLI will happily run something you did not pick.
Is this an OpenCode vs Claude Code benchmark?
No. The one same-task comparison in my log has no Claude run: eight runs of a cron-expression parser, one run per model, seven OpenCode models and one other tool's model. Six finished and passed hidden checks; two stalled with no code. With one run per model I do not rank them. If you also want Codex in the comparison, I wrote Codex vs Claude Code separately.
How I split work by task difficulty
This is my rule as written in the log, set after the tasks above. It is a policy, not a result of them.
| Task difficulty | Examples in my rule | What I use |
|---|---|---|
| Complex | architecture, schema migrations, concurrency, a release-blocking root cause, anything where a wrong answer ships broken | Claude Sonnet; if none is free, OpenCode on the paid plan |
| Standard | a feature slice in an existing pattern, a UI screen, tests | Gemini Flash through Google's Antigravity; if busy, OpenCode on the paid plan |
| Trivial | copy, release notes, docs, config, gate greps, mechanical edits | OpenCode free models only |
Keep reading
- Xcode MCP: Setting It Up for Claude Code, and What I SawThe three steps and one command Apple documents for external agents, then four things I observed on Xcode 27.0 that the page does not say.
- Gemini CLI vs Claude Code: What I Can and Cannot Say From My LogsA correction first: none of my logged runs used Gemini CLI. Here is what the Gemini rows are, what the two tools' docs say, and a dated tally with its limits.
- Using Codex as the Read-Only Reviewer for Claude CodeNot a benchmark: Claude Code writes and orchestrates, Codex reviews. The split, the verdict contract, and one case where the gate said no.