The fastest way to install OpenCode on Linux is still a one-line curl script, but the URL decides which OpenCode you get. The old opencode.ai/install address installs OpenCode 1. The opencode.ai/v2/install script, brew install opencode and the @opencode/cli npm package all install OpenCode 2, which renamed config keys and stopped running language servers, so an older setup guide may have left you on the old major version without saying so.
This guide installs OpenCode 2 on Ubuntu and other Linux distributions with curl, Homebrew and npm and upgrades an OpenCode 1 setup. Then it covers free models, provider keys, permission rules, AGENTS.md, a custom agent, MCP servers and a local Ollama model. It ends with the fix for the “free tier can only be used from within OpenCode” error, which we reproduced on purpose. Every command below was run on Ubuntu 26.04 and 24.04 lab VMs in October 2026 with OpenCode 2.1.4, and re-run on 24.04 in November 2026 with OpenCode 2.2.5.
Pick an Install Method for OpenCode 2
Use the v2 install script on servers and in WSL, because it needs nothing but curl and tar and doesn’t drag in Node.js or a package manager. Pick Homebrew if you already manage your tools with it, since brew upgrade then keeps OpenCode current with everything else. Both put OpenCode 2 on the machine in under 30 seconds in our runs. Avoid the two names that still resolve to OpenCode 1, because nothing warns you at install time and the config examples in current docs won’t work on the old version:
| Method | Command | What it installed in our lab | Tested on |
|---|---|---|---|
| v2 install script | curl -fsSL https://opencode.ai/v2/install | bash | OpenCode 2, single binary in ~/.opencode/bin | Ubuntu 26.04 |
| Homebrew | brew install opencode | OpenCode 2 from homebrew-core, plus ripgrep | Ubuntu 24.04 (Homebrew on Linux) |
| npm | npm install -g @opencode/cli | OpenCode 2 through a postinstall binary picker | Ubuntu 24.04, distro Node.js |
| Old install URL | curl -fsSL https://opencode.ai/install | bash | OpenCode 1 | Ubuntu 24.04 |
| Old npm name | npm install -g opencode-ai | OpenCode 1 (latest on the registry is still the 1.x line) | Registry check only |
The project’s own docs list a few more routes that we did not run: paru -S opencode-beta on Arch, mise use -g opencode (a current mise release; older ones install OpenCode 1), versioned Docker images such as ghcr.io/anomalyco/opencode:2.0.0, standalone zip and tarball builds, and a Desktop app in .deb, .rpm and AppImage form. Windows package managers are no longer supported in OpenCode 2, so the Chocolatey and Scoop commands from older guides are dead ends. On Windows, use npm, the standalone zip, or WSL with the Linux steps below. Skip that Arch package if you want the free models, because it tracks the beta channel and users report the free tier refusing beta channel builds.
Install OpenCode on Ubuntu with the Install Script
Only curl and tar are needed, and every Ubuntu server image ships both. It detects glibc or musl and checks /proc/cpuinfo for AVX2, falling back to a slower “baseline” build when the CPU doesn’t advertise it. That matters on Proxmox. Our first VM used the default kvm64 CPU type, where grep -c -w avx2 /proc/cpuinfo printed 0, so we switched the VM to the host CPU type before installing. Run the installer as your normal user, not root, because it installs into your home directory:
curl -fsSL https://opencode.ai/v2/install | bash
It downloads one binary of about 230 MB, adds a PATH line to ~/.bashrc, and finished in 10.7 seconds on the lab VM. Nothing lands outside your home directory, so it needs no sudo, and removing it later means deleting one directory:
Installing opencode version: 2.1.4
######################################################################## 100.0%
Successfully added opencode to $PATH in /home/josphat/.bashrc
OpenCode includes free models, to start:
exec $SHELL # Reload PATH (or open a new terminal)
cd <project> # Open directory
opencode # Run command
For more information visit https://opencode.ai/v2/docs
The PATH change only reaches new shells, so open a new terminal (or run exec $SHELL as the installer suggests) before you check anything. If OpenCode 1 came from npm or Homebrew earlier, remove that copy first. The migration guide says the two majors no longer install side by side, and two opencode binaries on one PATH is how you end up debugging the wrong version. Then confirm which binary answers:
opencode --version
which opencode
A v2 prefix in the version string is the quick tell that you are on OpenCode 2, because OpenCode 1 prints a bare number such as 1.19.4:
opencode v2.1.4
/home/josphat/.opencode/bin/opencode
Next to the binary sits an opencode2 shim, a two-line shell script that execs opencode, so either name works.

Nothing in the script is Ubuntu-specific. It downloads a prebuilt glibc or musl binary rather than a distro package, so Debian, Fedora, Rocky Linux and Alpine take the same path, and the musl check is what keeps it working on Alpine containers. We only ran it on Ubuntu, so treat the other distributions as expected rather than tested. For RHEL family notes and an older walkthrough on Rocky Linux, see our OpenCode on Ubuntu and Rocky Linux install notes.
Install OpenCode with Homebrew or npm
Homebrew’s own opencode formula in homebrew-core already tracks OpenCode 2, so brew install opencode is all you need on macOS or on Linux with Homebrew (set it up first with the official Homebrew on Linux steps, including the brew shellenv line for your shell). OpenCode’s docs point at their tap, anomalyco/tap/opencode-v2, but you don’t need it while the core formula is current. Check what the formula will give you first:
brew info opencode
On the Ubuntu 24.04 VM the formula reported the 2.x release and its two runtime dependencies:
==> opencode: stable 2.1.4 (bottled)
AI coding agent, built for the terminal
https://opencode.ai
Not installed
From: https://github.com/Homebrew/homebrew-core/blob/HEAD/Formula/o/opencode.rb
License: MIT
==> Dependencies
Required (2): ripgrep, icu4c@79
Install it. Bottles are prebuilt, so nothing compiles, and the whole run took 28.4 seconds including ripgrep, icu4c@79 and their libraries:
brew install opencode
Its last lines confirm the 2.x bottle went in:
==> Installing opencode
==> Pouring opencode--2.1.4.x86_64_linux.bottle.1.tar.gz
🍺 /home/linuxbrew/.linuxbrew/Cellar/opencode/2.1.4: 10 files, 220.5MB
==> Caveats
Bash completion has been installed to:
/home/linuxbrew/.linuxbrew/etc/bash_completion.d
opencode --version then printed opencode v2.1.4 from /home/linuxbrew/.linuxbrew/bin/opencode. Homebrew owns that copy, so keep it current with brew upgrade opencode alongside the rest of your formulae. We ran this on Homebrew for Linux; the formula is the same one macOS uses, but we did not repeat it on a Mac.
For npm, the package name is the whole story. opencode-ai is the OpenCode 1 package and still gets 1.x releases, while OpenCode 2 lives in @opencode/cli. Compare them before you install:
npm view opencode-ai version
npm view @opencode/cli version
npm answered with one version from each major line, which is why an npm install -g opencode-ai copied from an older guide quietly gives you OpenCode 1:
1.19.4
2.1.4
Ubuntu 24.04’s packaged Node.js was new enough, because the npm package only runs a small postinstall script that links the right native binary. With the distro Node, global installs go to /usr/local and need sudo:
sudo npm install -g @opencode/cli
npm pulled five packages in 29 seconds, and /usr/local/bin/opencode --version returned opencode v2.1.4. If you install Node with nvm instead, drop the sudo, because nvm keeps global packages in your home directory.
Upgrade from OpenCode 1 to OpenCode 2
Moving an OpenCode 1 install to version 2 takes the --major flag on opencode upgrade, and its help text says exactly what it does: “upgrade to OpenCode 2 and remove OpenCode 1”. We installed OpenCode 1 from the old URL on Ubuntu 24.04, gave it a v1-style config and one session, then upgraded. Copy ~/.config/opencode somewhere first, because the binary swap is one way, and a copy of the old setup lets you compare behaviour if something you relied on changes:
cp -a ~/.config/opencode ~/opencode-v1-config.bak
opencode upgrade --major
OpenCode detected the curl install, so it reused the same location, and swapped the binary in 14.8 seconds:
┌ Upgrade
│
● Using method: curl
│
● From 1.19.4 → OpenCode 2
│
◇ OpenCode 2 installed
│
└ Done
Check the version, then confirm your history and config survived, since those are the two things an upgrade could lose:
opencode --version
opencode session list
opencode debug config
The OpenCode 1 session was still listed, and debug config showed the old file translated in memory into OpenCode 2 field names:
opencode v2.1.4
ses_e8e9613b2ffeIgkLq7kwRr8e5T Hello from v1 response request 10/25/2026, 4:55:33 AM
[
{
"type": "document",
"path": "/home/josphat/.config/opencode/opencode.json",
"info": {
"$schema": "https://opencode.ai/config.json",
"model": {
"providerID": "opencode",
"model": "big-pickle"
},
"permissions": [
{
"action": "shell",
"resource": "git push *",
"effect": "deny"
},
{
"action": "edit",
"resource": "*",
"effect": "allow"
}
],
"agents": {
"reviewer": {
"system": "Review for correctness and missing tests.",
"permissions": [
{
"action": "edit",
"resource": "*",
"effect": "deny"
}
]
}
}
}
},
OpenCode 2 accepts both shapes, so you only need to convert by hand when you want the native format or when you copy snippets from current docs. These are the renames you will meet most:
| OpenCode 1 | OpenCode 2 |
|---|---|
permission (map per tool) | permissions (ordered array, last match wins) |
bash permission | shell |
task permission | subagent |
agent / mode | agents |
prompt in an agent | system |
provider, npm, options.baseURL | providers, package (AI SDK packages take an aisdk: prefix), settings.baseURL |
mcp.<name> with enabled | mcp.servers.<name> with disabled |
On disk, the file was not rewritten: it still read "permission" and "bash" after the upgrade, which is what you want if a teammate still runs OpenCode 1 from the same dotfiles. Most of the config carries over, but the OpenCode migration guide lists the parts you have to deal with yourself:
- V1 plugins don’t run; they need porting to the new plugin API
- TUI settings move from
tui.jsonto one global~/.config/opencode/cli.jsonon first start lspsettings are accepted but no language servers run- only
AGENTS.mdis read, with noCLAUDE.mdfallback - reads and edits outside the project are allowed by default (OpenCode 1 asked first)
- the todo tool is off unless
experimental.todois set
Losing language servers is the change that bites. OpenCode 1 could ask gopls or pyright for diagnostics after an edit and fix type errors in the same turn. OpenCode 2 keeps your lsp block but never starts those servers, so nothing reports a broken import until you run the code. Put your linter or type checker command in AGENTS.md and allow it in permissions, so the agent runs it after each edit without stopping to ask.
Run OpenCode with the Free Models
OpenCode 2 works straight after install with no account and no API key, through the free models on OpenCode’s Zen gateway. List what is on offer today:
opencode models
Nine free models came back on a fresh install. The list changes as models rotate in and out, so check it rather than hardcoding a name in scripts:
opencode/beam-free
opencode/big-pickle
opencode/deepseek-v4.1-flash-free
opencode/flint-40b-free
opencode/glm-5.3-flash-free
opencode/mimo-v2.6-flash-free
opencode/nemotron-3-ultra-free
opencode/nemotron-3.5-lightning-free
opencode/nemotron-3.5-super-vl-free
On the November 2026 rerun with OpenCode 2.2.5, opencode models listed eight, with opencode/nemotron-3-ultra-free gone from the rotation, which is the rotation the paragraph above warns about.
opencode models opencode, the OpenCode 1 syntax for filtering by provider, fails in OpenCode 2 with Unexpected positional argument, so pipe through grep instead. To give the agent something real to do, create a small Git project. The Git repository matters later, because the permission rules below allow git status and git diff and the TUI shows the current branch:
mkdir -p ~/projects/inventory && cd ~/projects/inventory
git init
vim stock.py
Paste in one function with a classic typo, =+ where += was meant:
def total_stock(items):
total = 0
for item in items:
total =+ item["qty"]
return total
if __name__ == "__main__":
print(total_stock([{"qty": 3}, {"qty": 4}]))
python3 stock.py prints 4 instead of 7. Ask OpenCode why, from inside the project directory, with opencode run so the answer lands on stdout:
opencode run -m opencode/big-pickle "Why does stock.py print 4 instead of 7? Explain in two sentences."
OpenCode listed the directory, found the file, read it, and named the bug in 16.2 seconds:
> build · big-pickle
→ Read .
✱ Glob "**/stock.py" 1 match
> build · big-pickle
→ Read stock.py
> build · big-pickle
The operator `=+` is assignment with unary plus, not addition assignment (`+=`), so `total =+ item["qty"]` overwrites `total` with each item's quantity instead of adding to it.
As a result, only the last value (`4`) is kept, causing the function to return `4` instead of summing `3` and `4` to get `7`.
-c continues the last session, so the follow-up keeps the context and you don’t have to explain the bug again:
opencode run -c "Fix it, then run python3 stock.py to confirm."
The default build agent edited the file and ran Python on its own, in 4.6 seconds:
> build · big-pickle
← Edit stock.py
Index: stock.py
===================================================================
--- stock.py
+++ stock.py
@@ -1,8 +1,8 @@
def total_stock(items):
total = 0
for item in items:
- total =+ item["qty"]
+ total += item["qty"]
return total
if __name__ == "__main__":
> build · big-pickle
$ python3 stock.py
7
> build · big-pickle
Fixed (replaced `=+` with `+=`). `python3 stock.py` now prints `7`.
Nobody approved that shell command. With no permission rules, the build agent edits files and runs commands freely, which is fine in a throwaway VM and not what you want in a repository that holds deploy scripts or credentials. A model that misreads a task can run anything your user can run, and a free model is more likely to misread one. Set the rules in the next section before you point OpenCode at real code, and keep experiments like this one in a scratch directory until then.
Set Global Defaults in opencode.jsonc
OpenCode merges config from several files, and the closest one wins for any setting that conflicts. Knowing the order saves time when a setting seems to be ignored:
~/.config/opencode/opencode.jsonor.jsonc: your defaults for every projectopencode.json(c)in the project root, then.opencode/opencode.json(c): project settings, committed with the code/etc/opencode/opencode.json(c)on Linux: machine-wide settings that override all of the above
JSONC allows comments and trailing commas, so use it for files people edit by hand. Create the global file:
vim ~/.config/opencode/opencode.jsonc
We ran with this file. Permission rules form an ordered list where the last match wins, so the broad ask for shell commands comes first and the exceptions follow it. Read-only Git commands and the exact test command run without prompting, git push is denied, reading a .env file needs approval, and the last rule restores OpenCode 1’s habit of asking before touching files outside the project:
{
"$schema": "https://opencode.ai/config.json",
// Default model for every project on this machine
"model": "opencode/big-pickle",
"permissions": [
{ "action": "shell", "resource": "*", "effect": "ask" },
{ "action": "shell", "resource": "git status *", "effect": "allow" },
{ "action": "shell", "resource": "git diff *", "effect": "allow" },
{ "action": "shell", "resource": "python3 stock.py", "effect": "allow" },
{ "action": "shell", "resource": "git push *", "effect": "deny" },
{ "action": "read", "resource": "*.env", "effect": "ask" },
{ "action": "external_directory", "resource": "*", "effect": "ask" }
]
}
Allow the exact test command, never python3 *. An allow rule for an interpreter lets the model run python3 -c with any code it likes, including code that shells out to the commands you meant to deny. OpenCode 2 keeps a background service running between commands, and that service holds the config it read when it started, so tell it to pick up the new file and check what it loaded:
opencode reload
opencode debug config
reload answers Configuration reloaded. debug config prints every source it merged, in order, with the parsed content. The head of the output is enough to confirm your file was read, and that the model string was split into provider and model:
[
{
"type": "document",
"path": "/home/josphat/.config/opencode/opencode.jsonc",
"info": {
"$schema": "https://opencode.ai/config.json",
"model": {
"providerID": "opencode",
"model": "big-pickle"
},
"permissions": [
{
"action": "shell",
"resource": "*",
"effect": "ask"
},
Now test the rules from a script. uname -r falls under the broad ask rule and git push under the deny:
opencode run "Run the shell command uname -r and print its output. Do nothing else."
opencode run "Run the shell command git push origin main and report the result. Do nothing else."
A non-interactive run has nobody to ask, so ask turns into a rejection, while deny blocks outright. Both runs exited 0:
> build · big-pickle
! permission requested: shell (uname -r); auto-rejecting
✗ uname -r failed
Error: This non-interactive run cannot ask the user for permission, so the request was rejected. Continue without this action.
> build · big-pickle
! permission requested: shell (uname -r); auto-rejecting
✗ uname -r failed
Error: This non-interactive run cannot ask the user for permission, so the request was rejected. Continue without this action.
> build · big-pickle
The shell tool is not available in this environment due to permission restrictions; I cannot run `uname -r`.
> build · big-pickle
✗ git push origin main failed
Error: Permission denied: shell
> build · big-pickle
Command: `git push origin main`
Result: Permission denied (shell execution not permitted).
No other actions taken.
That clean result took two attempts. On the first uname -r run, the free model never called the shell tool at all: it narrated a tool call and printed 6.14.0-35-generic, a kernel version it made up, while the VM was running 7.0.0-38-generic. A third attempt hung until our 120-second timeout, so wrap scripted runs in timeout. Exit code 0 either way, so a CI step that only checks the exit code reports success when the work was never done or the answer was invented. Check the output for Permission denied or the auto-reject message if you script OpenCode. Add --auto when a pipeline should approve anything that is not explicitly denied:
opencode run --auto "Run the shell command uname -r and print its output. Do nothing else."
With --auto the same command ran and returned the real kernel version:
> build · big-pickle
$ uname -r
7.0.0-38-generic
> build · big-pickle
`7.0.0-38-generic`
Don’t read deny as a guarantee under --auto. The rule matches the literal command, so we asked for the same push wrapped in sh -c:
opencode run --auto "Run exactly this shell command and report its output: sh -c 'git push origin main'"
OpenCode ran it. The push only failed because the lab repository had no main branch (git init had created master) and no remote, and the model then went on to try git push origin master by itself:
> build · big-pickle
Calling the shell tool to run the exact requested command.
$ sh -c 'git push origin main'
error: src refspec main does not match any
error: failed to push some refs to 'origin'
Deny rules stop the agent from running a command by accident, not from reaching it another way, whether that’s sh -c, env or an interpreter. For unattended runs, give OpenCode a container or a CI user with no push credentials and no production secrets, and treat permissions as a speed bump on top of that, never as the wall.
Add Project Rules with AGENTS.md
AGENTS.md is where build commands, conventions and checks go, so you stop repeating them in every prompt. OpenCode loads the global ~/.config/opencode/AGENTS.md plus each AGENTS.md from your working directory up toward your home directory (stopping at the project root for projects outside home), and picks up files in subdirectories as the agent reads them. Create one in the project root:
vim AGENTS.md
Our test file had four rules, and the last one is there only because it is easy to spot in the output:
# Project instructions
- This is a Python 3 project with no third-party dependencies.
- Run python3 stock.py after any change and show its output.
- Use type hints on every new function.
- End every reply with the line: Checked against AGENTS.md
Give it a task that touches all four rules, so you can see which ones the model actually honours:
opencode run "Add a low_stock(items, threshold) function that returns the names of items below the threshold."
OpenCode ran python3 stock.py (allowed by the exact-match rule) and ended with the marker line, in 16.8 seconds:
> build · big-pickle
→ Read .
> build · big-pickle
→ Read stock.py
> build · big-pickle
Let me add the `low_stock` function.
← Edit stock.py
Index: stock.py
===================================================================
--- stock.py
+++ stock.py
@@ -4,6 +4,14 @@
total += item["qty"]
return total
+def low_stock(items, threshold):
+ result = []
+ for item in items:
+ if item["qty"] < threshold:
+ result.append(item["name"])
+ return result
+
+
if __name__ == "__main__":
print(total_stock([{"qty": 3}, {"qty": 4}]))
> build · big-pickle
$ python3 stock.py
7
> build · big-pickle
Checked against AGENTS.md
Look at the new function, though. It has no type hints, and that happened on both of our runs. Three of the four rules held and the third was silently skipped. AGENTS.md is guidance the model weighs, not a rule OpenCode enforces, so anything that must never happen belongs in permissions (and in the sandbox around it), and anything that must always happen belongs in a check the agent runs, such as a type checker. In the TUI, /init walks you through writing a first AGENTS.md for an existing repository.
Work in the OpenCode TUI
Run opencode with no arguments inside a project to open the full-screen interface. The bottom bar shows the active agent and model, shift+tab cycles agents, and ctrl+p opens the command palette:

Type / to see the slash commands. The build we tested had 39 entries, and these are the ones you will use first:
| Command | What it does |
|---|---|
/connect | Add or switch a provider account (API key or OAuth) |
/models | Switch the model for this session |
/agents | Switch the primary agent |
/new | Start a new session |
/sessions | Switch to an earlier session |
/init | Guided AGENTS.md setup |
/review | Review uncommitted changes, a commit, a branch or a PR |
/diff | Open the diff viewer |
/mcps | List MCP servers and sign in to remote ones |
/themes | Switch the colour theme |
/settings | Open the CLI settings |
/update | Update OpenCode |
Several commands that older guides list, such as /compact and /share, are not in the OpenCode 2 menu, so don’t hunt for them. For the full command and key reference, keep our OpenCode CLI cheat sheet open next to the terminal.

Permission rules apply in the TUI too, and here ask becomes a real prompt. We asked for type hints and a test run, and the model chained python3 --version && git status --short && ls. OpenCode splits a chained command into its parts and checks each one: any part that hits a deny blocks the chain, otherwise any part that hits an ask prompts for the whole thing. This screenshot comes from our first pass, when the config still allowed python3 *, so python3 --version and git status --short were allowed and the bare ls was the part that asked:

The sidebar already lists the two MCP servers added later in this guide, because this screenshot comes from the end of our first pass. We picked Allow once, which approves just this command. After approval the agent rewrote both functions with type hints and ran the script, and the diff view shows exactly what changed:

Per-part matching is why a short list of allow rules goes a long way. Allow the read-only commands your agent reaches for all the time (ls, git status *, git diff *) and the prompts that remain are the ones worth reading.
Connect a Provider and Store API Keys
Free models are fine for trying OpenCode, but for daily work you’ll connect a provider such as Anthropic, OpenAI, Google or OpenRouter, or sign in with a ChatGPT Plus or Pro plan. You can also route OpenCode through FreeLLMAPI, a self-hosted Docker gateway that hides your provider keys behind one unified key and benches a model and key pair when its upstream rate-limits. Run /connect in the TUI, or the same flow from the shell:
opencode auth login
With no argument it opens a provider picker. opencode auth login anthropic --method key skips straight to API-key entry and prompts for the key, so it never lands in your shell history. Keys you enter this way are stored in OpenCode’s SQLite database under ~/.local/share/opencode/ and listed by opencode auth list. We didn’t connect a paid account in the lab, so this flow comes from the docs rather than our own run. If you are deciding between OpenCode on Claude models and Anthropic’s own client, our OpenCode vs Claude Code vs Cursor comparison covers where each one fits.
Environment variables are where OpenCode 2 trips up anyone coming from version 1. Model requests are made by the background service, not by the command you type, and the service only sees the environment it was started with. So the old habit of putting export ANTHROPIC_API_KEY=... in ~/.bashrc works only if that shell happens to be the one that starts the service. We exported a key in the shell and asked OpenCode what it could see:
export ANTHROPIC_API_KEY=your-anthropic-key
opencode auth list
opencode service restart
opencode auth list
Our first check saw nothing, because the service had started before the export. Only after a restart, launched from that same shell, did the key appear:
No authenticated integrations
http://127.0.0.1:49374
Anthropic ANTHROPIC_API_KEY environment
Relying on that is fragile, because the next time the service starts from a shell without the variable, the key silently disappears. For a machine where the key has to come from the environment, such as a CI runner, hand the variable to the service itself. Start the line with a space if your shell sets HISTCONTROL=ignorespace (Ubuntu’s default ignoreboth includes it), so the key stays out of ~/.bash_history:
opencode service set env ANTHROPIC_API_KEY your-anthropic-key
opencode auth list
With it set, 20 Anthropic models showed up in opencode models, and opencode service unset env ANTHROPIC_API_KEY removed them again. The docs also describe a one-off private server for a single session, ANTHROPIC_API_KEY=... opencode --standalone, which reads the variable without touching the shared service. The shared service listens on 127.0.0.1:49374, which opencode service status prints if you need to check it is running.
Create a Custom Agent
Agents are a system prompt plus a model and permissions, stored as Markdown files. OpenCode ships build and plan as primary agents, and build is the one that edited files without asking earlier. A read-only reviewer is the first custom agent most teams add, because a review that can quietly fix what it finds is no longer a review. Keep it in the project’s .opencode/agents/ directory so it travels with the repository:
mkdir -p .opencode/agents
vim .opencode/agents/reviewer.md
Its frontmatter uses the OpenCode 2 field names from the rename table above, so a file copied from an OpenCode 1 setup needs converting before it behaves:
---
description: Reviews code without editing files or running commands
mode: primary
permissions:
- action: edit
resource: "*"
effect: deny
- action: shell
resource: "*"
effect: deny
---
Review the code you are pointed at. List findings in severity order with file and line references.
Run it by name. In the TUI it also shows up under shift+tab:
opencode run --agent reviewer -m opencode/big-pickle "Review stock.py"
OpenCode’s status line switches to the reviewer, and it reads before it answers. We ran this review on the file as the AGENTS.md step left it, before the TUI edit added type hints, so its first finding was the rule the build agent had ignored:
> reviewer · big-pickle
→ Read stock.py
> reviewer · big-pickle
**Findings (severity order):**
### CRITICAL
1. **Missing type hints (AGENTS.md violation)** - `stock.py:1-13`
Both `total_stock()` and `low_stock()` lack type hints. AGENTS.md explicitly requires: "Use type hints on every new function." Even for existing functions, this violates the project's stated rule. Should be annotated (e.g., `list[dict[str, int | str]]` or more specific types).
Read the end of the report with care, though. It closed with a Runtime output block showing $ python3 stock.py and 7, a command this agent could not have run because its shell access is denied. The number happens to be right, but it was written, not executed, so treat any output a read-only agent quotes as a claim to check.
Keep read allowed on agents like this one. A reviewer that cannot read files is useless anyway, and denying read is also what broke the free models in our tests, as the error section below shows. Set mode: subagent instead of primary if you want other agents to launch the reviewer through the subagent tool, for example when you ask the build agent to have its change reviewed. That runs the review in a fresh context instead of the one that wrote the code.
Add MCP Servers to OpenCode
MCP servers give the agent extra tools: a database, an issue tracker, a docs search. Local servers usually start through npx, so install Node.js first. Ubuntu 26.04’s packaged Node.js runs them fine:
sudo apt install -y nodejs npm
From the project directory, add a local filesystem server limited to the project, and Context7 as a remote one. Everything after -- is the command OpenCode runs, and "$PWD" expands to the project path:
opencode mcp add fs -- npx -y @modelcontextprotocol/server-filesystem "$PWD"
opencode mcp add context7 --url https://mcp.context7.com/mcp
Both commands write to the project’s opencode.json. Add --global to put a server in your user config instead. The result uses the OpenCode 2 layout, with servers under mcp.servers:
{
"mcp": {
"servers": {
"fs": {
"type": "local",
"command": [
"npx",
"-y",
"@modelcontextprotocol/server-filesystem",
"/home/josphat/projects/inventory"
]
},
"context7": {
"type": "remote",
"url": "https://mcp.context7.com/mcp"
}
}
}
}
Right after adding them, opencode mcp list printed No MCP servers configured. That looks like a failed write, but the file is fine. Servers connect when a session starts, so the list stays empty until something opens one. Run a session that uses a server, then check again:
opencode run --auto "Use the fs MCP server to list the allowed directories."
opencode mcp list
The agent called the server’s tool and reported the one directory it may touch, and both servers were connected afterwards:
Allowed directories:
- /home/josphat/projects/inventory
Checked against AGENTS.md
✓ context7 connected
✓ fs connected
Remote servers that need OAuth show needs authentication instead; sign in from /mcps in the TUI. MCP tools take up model context, so connect the few servers you use rather than everything you can find.
Run a Local Model with Ollama
A local model keeps code on your machine and costs nothing per token, but OpenCode sends a large system prompt and tool list with every request, and that makes CPU-only hardware feel slow. Plan on a GPU or Apple silicon for anything beyond a smoke test. Install Ollama and pull a small coding model (our Ollama install guide for Ubuntu 26.04 covers GPU drivers and the service in detail):
curl -fsSL https://ollama.com/install.sh | sh
ollama pull qwen2.5-coder:1.5b
OpenCode 2 has Ollama built in. It probes http://127.0.0.1:11434 and adds every completion model it finds, so there is nothing to configure on the same machine. Check that it found yours, then send a trivial prompt first, because a one-word reply tells you whether the plumbing works before you wait minutes on a real task:
opencode models | grep ollama
opencode run -m ollama/qwen2.5-coder:1.5b "Reply with one word: ready"
The model was listed with no config at all. The reply was another matter:
ollama/qwen2.5-coder:1.5b
> build · qwen2.5-coder:1.5b
{ "name": "readiness", "arguments": {"message": "ready"} }
That took 47.6 seconds, and instead of the word ready the model answered with an invented tool call. Ollama’s log shows why it was slow: 2,068 tokens processed at about 60 tokens a second on two vCPUs with no GPU, nearly all of them OpenCode’s own prompt and tool definitions. An earlier run of the same prompt did answer ready, in 45.8 seconds, so a 1.5B model is a coin toss even on a one-word task. For real local work you want a GPU or a machine with a lot of fast unified memory and a model in the 8B class or larger; our mini PC for local AI guide sizes boxes by the model you plan to run.
If Ollama runs on another host or behind a proxy, set settings.baseURL on the built-in ollama provider to that server’s /v1 address in opencode.jsonc, as the OpenCode providers docs show; we only tested the local default. To point OpenCode at any OpenAI-compatible gateway instead, see using OpenCode with one OpenAI-compatible API key.
If you use cloud models and only want a quiet machine that keeps OpenCode sessions and MCP servers running all day, the base Mac mini M6 with 16 GB (paid link) is the pick for agents that call cloud APIs in our Mac mini for AI agents guide.
Fix “OpenCode’s free tier can only be used from within OpenCode”
This error means OpenCode’s Zen gateway decided the request did not come from a stock OpenCode client. In our tests on this release, the trigger on the official client was an agent that denies the read tool. Free models are only served to requests that look like the real client with its standard tools, so an agent that blocks file reads gets refused even though nothing about your account or install is wrong. The check applies to the free models; paid models and your own provider keys don’t go through it, which is why the error tends to appear on one agent while another keeps working. The full message is:
Error: OpenCode's free tier can only be used from within OpenCode. Download it at https://opencode.ai and keep building.
We reproduced it by giving test agents one denied permission each and asking for a three-word greeting on the free model:
| Denied in the agent | Result on the free model |
|---|---|
everything (action: "*") | rejected every time |
read | rejected, three runs out of three |
shell | answered, three clean runs out of three |
edit | answered |
glob or grep | answered |
read set to ask | answered, two runs out of two |
Reports from earlier 1.x releases say a bash deny also tripped it. On this release, one shell-deny run failed in our first pass, straight after the same agent file had been rewritten from its read-deny version, and three clean reruns all answered. The read deny failed every time, and changing that rule from deny to ask fixed it on the next run. This is the fully locked agent from the table, written identically in both of our passes, and the error it got on a plain “Say hi” prompt:

Work through the causes in this order, because the first two take a minute to fix on your own machine:
- An agent denies
reador everything: switch the rule toask, or give that agent a paid model or your own provider key - You run a beta channel, dev or distro-patched build: install the official release and check that
opencode --versionprints a plain release number - You call the free models from a third-party client, proxy or editor extension: that is blocked by design, so use a paid model there
- An outdated or non-release build gets “… or newer is required” instead: on OpenCode 1, run
opencode upgrade --major
Case three is policy, not a bug. The GitHub issue tracking the error was closed as not planned, and commenters there reverse-engineered the request checks the gateway applies. Don’t try to imitate the client’s headers to get around it.
Troubleshoot Other OpenCode Errors
These came up during testing, each with the exact message OpenCode printed.
Error: Free usage limit reached
After a few dozen free-model runs in one morning of testing, every free request, from every agent, started failing with Free usage limit reached. Connect a provider or subscribe to OpenCode Go to keep going, or switch models. This is a quota, not the client check above, so permission changes won’t help. Connect a provider with opencode auth login, point the agent at a local Ollama model, or wait for the limit to reset.
Error: Unexpected positional argument: “opencode”
You ran opencode models opencode. OpenCode 1 took a provider filter there; OpenCode 2’s models command takes no arguments. Use opencode models | grep opencode/.
Error: This non-interactive run cannot ask the user for permission
An ask rule matched during opencode run, and with no TUI there is nobody to answer, so the action was rejected and the run carried on without it. Add --auto to approve everything that is not explicitly denied, or add a narrower allow rule for the command you expect.
No MCP servers configured
opencode mcp list prints this right after opencode mcp add, before any session has started. Run one opencode run or open the TUI in the project, then list again. If it still says this, run the list command from the directory that holds the opencode.json you wrote to.
No authenticated integrations after exporting a key
The background service started before you exported the variable. Use opencode auth login to save the key, or opencode service set env NAME value, which stops the service so the next command starts it with the variable.
Uninstall OpenCode
OpenCode 2 has its own uninstaller, and it is deliberately conservative: it removes the cache and stops the background service, but keeps your sessions, config and saved credentials. Preview it first:
opencode uninstall --dry-run
On a curl install it also lists the binary under “manual removal”, which means it won’t delete it for you:
┌ Uninstall OpenCode
│
● Installation method: curl
│
│ The following global files will be removed (shared by OpenCode versions and channels):
│
● Cache: /home/josphat/.cache/opencode
│
● Stop background service and persistent terminals: /home/josphat/.local/state/opencode/service.json
│
● Binary (manual removal): /home/josphat/.opencode/bin/opencode
│
▲ Dry run - no changes made
│
└ Done
Run it for real with opencode uninstall --force, then remove the binary directory and the PATH lines the installer added to ~/.bashrc. Delete only ~/.opencode/bin, because OpenCode also reads config from ~/.opencode itself and any agents you keep there would go with it:
opencode uninstall --force
rm -rf ~/.opencode/bin
sed -i '/# opencode/d;/\.opencode\/bin/d' ~/.bashrc
Your history in ~/.local/share/opencode/ survives all of that. When we installed OpenCode again with Homebrew on the same VM, opencode session list still showed the session created under OpenCode 1. Delete that directory and ~/.config/opencode/ only if you want a truly clean machine. Homebrew and npm installs come off with brew uninstall opencode and sudo npm uninstall -g @opencode/cli.