How you upgrade OpenCode on Linux depends on which package manager put the first copy there. On Rocky Linux an npm install of OpenCode 1 refuses to upgrade itself and prints only “exit code 243”. On Arch the extra package is already OpenCode 2, while the AUR’s opencode-bin is still OpenCode 1, and Debian has no apt package at all.
This guide covers the install path on Rocky Linux and RHEL, Debian and Ubuntu, Arch and Homebrew on Linux, npm versus the binary, and the OpenCode 1 to 2 upgrade with the migration errors we hit. For first-run setup (free models, AGENTS.md, providers), use our OpenCode 2 setup guide. The installs, upgrades and errors below were run in October 2026 on Rocky Linux 10, Debian 13 and Arch Linux VMs, upgrading OpenCode 1.19.4 to 2.2.0, and the few steps we did not run are marked.
Pick the Install Path for Your Distro
Use the native package where one exists and the v2 install script everywhere else. Arch is the only distro here with an official OpenCode package, and none of the RHEL or Debian repositories carries the CLI: apt-cache policy opencode on Debian 13 printed nothing, and the only .deb and .rpm files upstream publishes are for the Desktop app. This is what each path gave us on the lab VMs:
| Distro | Method | Installs | Install time | On disk | Upgrade with |
|---|---|---|---|---|---|
| Rocky Linux 10 / RHEL | v2 curl script | OpenCode 2 | 10 s | 236 MB in ~/.opencode | opencode upgrade |
| Rocky Linux 10 / RHEL | npm @opencode/cli | OpenCode 2 | 24 s | about 940 MB | sudo npm install -g @opencode/cli@latest |
| Debian 13 / Ubuntu | v2 curl script | OpenCode 2 | 9 s | 236 MB | opencode upgrade |
| Arch Linux | pacman -S opencode | OpenCode 2 | 9 s | 234 MB with ripgrep | pacman -Syu |
| Arch Linux | AUR opencode-bin | OpenCode 1 | 8 s build | 205 MB | avoid |
| Any (Homebrew on Linux) | brew install opencode | OpenCode 2 | 20 s | 217 MB plus icu4c and ripgrep | brew upgrade |
Two numbers in that table decide most cases. The npm package carries every x64 Linux build (glibc, musl, and the baseline builds for CPUs without AVX2) at about 235 MB each, so it uses four times the disk of the curl script for the same binary. And pacman and Homebrew own their copies, so opencode upgrade either refuses to touch them or shouldn’t. On a server we’d take the curl script. On Arch, pacman. On a workstation where brew already manages your CLI tools, Homebrew.
Install OpenCode on Rocky Linux and RHEL
Rocky Linux 10 cloud images ship without git, and OpenCode tracks projects by their git repository, so install git first. Our first git init in the demo project failed with “git: command not found” until we did:
sudo dnf install -y git curl tar
Then run the v2 install script as your normal user. It needs no sudo because everything lands in your home directory:
curl -fsSL https://opencode.ai/v2/install | bash
It finished in 10 seconds and added one PATH line under a # opencode marker in ~/.bashrc:
Installing opencode version: 2.2.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
SELinux stays enforcing. The binary came out of the tarball labelled user_tmp_t instead of user_home_t, which looks wrong in ls -Z but made no difference: OpenCode ran, upgraded and served sessions, and sudo ausearch -m avc -ts today printed “<no matches>” at the end of the lab, because targeted policy does not confine binaries an unconfined user runs from home. Open a new shell and check the version:
exec $SHELL
opencode --version
ls -laZ ~/.opencode/bin/
A v prefix means OpenCode 2, because OpenCode 1 prints a bare number. The small opencode2 file is a two-line shell shim that execs opencode:
opencode v2.2.0
-rwxr-xr-x. 1 josphat josphat unconfined_u:object_r:user_tmp_t:s0 246831280 Oct 26 1985 opencode
-rwxr-xr-x. 1 josphat josphat unconfined_u:object_r:user_home_t:s0 47 Oct 28 01:05 opencode2
If you’d rather manage it with npm, Rocky Linux 10’s AppStream has Node.js 22 as a plain package. There are no module streams on EL10 any more, so dnf module list nodejs returns “No matching Modules to list” and you install it directly:
sudo dnf install -y nodejs npm
sudo npm install -g @opencode/cli
That gave us Node.js 22 with npm 10, and the OpenCode install added 5 packages in 23 seconds. The package name matters more than anything else here: @opencode/cli is OpenCode 2, while opencode-ai, the name in most older guides, is OpenCode 1 and still gets 1.x releases. Global installs under the distro’s Node go to /usr/local as root, which is also why the npm upgrade path later needs sudo.
Install OpenCode from the Arch extra Repo
Arch packages OpenCode 2 in the official extra repository, so pacman is the install to use and no AUR helper is needed. Bring the system up to date in the same transaction, because Arch does not support partial upgrades:
sudo pacman -Syu opencode
Pacman pulls ripgrep as the only new dependency and lists wl-clipboard and xclip as optional for clipboard support. The package ships /usr/bin/opencode plus bash, fish and zsh completions, and it installed in 8.7 seconds. The extra package trailed upstream by one minor release on the day we tested: it was flagged out of date while the newer build sat in extra-testing, which most machines don’t enable. That lag is normal for Arch and usually a matter of days, so let pacman -Syu catch up rather than mixing in another install method.
The AUR is where people end up on the wrong major. opencode-bin still builds OpenCode 1. Despite its name, opencode-beta is upstream’s own AUR package of the current 2.x release (the OpenCode docs list paru -S opencode-beta as the Arch install), while opencode-v2-git builds an older snapshot from git. The AUR RPC showed it plainly:
curl -s "https://aur.archlinux.org/rpc/v5/info?arg[]=opencode-bin&arg[]=opencode-beta&arg[]=opencode-v2-git" | python3 -c "import sys,json;[print(r[\"Name\"],r[\"Version\"],r.get(\"OutOfDate\"),r.get(\"Conflicts\"),r.get(\"Provides\")) for r in json.load(sys.stdin)[\"results\"]]"
All three declare a conflict with the extra opencode package, and only opencode-beta matches the current upstream release:
opencode-bin 1.19.4-1 None ['opencode'] ['opencode']
opencode-beta 2.2.0-1 None ['opencode', 'opencode2'] ['opencode', 'opencode2']
opencode-v2-git 2.0.30.412b2b1d85-1 None ['opencode', 'opencode-bin', 'opencode-git'] ['opencode', 'opencode-v2']
We built opencode-bin to check, and its PKGBUILD downloads the 1.x release tarball from GitHub. Installing it over the extra package stops at a conflict, and with --noconfirm pacman answers “N” and leaves OpenCode 2 alone. The capture below also shows the second Arch surprise: opencode upgrade can’t tell how a pacman-owned binary was installed, so it refuses instead of overwriting /usr/bin/opencode:

That refusal is the right behaviour, and pacman -Qk opencode confirmed all 16 package files intact afterwards. We would not pass --method curl to force it (we didn’t try): the curl method installs into ~/.opencode/bin, so you’d end up with a second copy ahead of the pacman one on your PATH. Between extra and opencode-beta, we’d stay on extra because it is signed and built by Arch, and accept that it trails upstream by a few days. If pacman is stuck on a lock while you do any of this, our note on the “pacman is currently in use” error covers the stale lock file.
Install OpenCode on Debian 13 and Ubuntu
With no apt package, Debian and Ubuntu come down to the curl script or npm, and the script wins by a wide margin. The Debian 13 cloud image had curl, tar and ca-certificates but no git and no unzip. The script does not need unzip, but install git anyway: our first opencode run on that VM, before git was installed, sat for two minutes with no output until the timeout killed it, and the same prompt answered in under 5 seconds once git was there. Install git and run the script:
sudo apt update
sudo apt install -y git curl
curl -fsSL https://opencode.ai/v2/install | bash
On Debian, npm is where the cost shows. Debian 13 packages Node.js 20, and sudo apt install -y nodejs npm on the cloud image reported “581 newly installed” packages, a 199 MB download, 880 MB of extra disk and 5 minutes 26 seconds before npm could even start. If Node isn’t already on the box for something else, that is a lot to install for one binary.
Having both copies installed is the other Debian and Ubuntu trap, because which one answers depends on the shell. The installer appends its PATH line to the end of ~/.bashrc, and Debian’s default .bashrc returns early for non-interactive shells, before it reaches that line. So cron, CI and ssh host cmd find only the npm copy in /usr/local/bin, while your login shell finds the curl copy first. Check what a login shell sees:
bash -lic 'which -a opencode'
With both installed on the Debian VM, the curl copy came first:
/home/josphat/.opencode/bin/opencode
/usr/local/bin/opencode
Two copies at different versions means a script and your terminal run different OpenCodes against the same config and session database, so keep exactly one. We ran these steps on Debian only. The script downloads a prebuilt glibc binary and touches nothing distro-specific, and our Ubuntu setup guide ran the same script on Ubuntu 26.04.
Install OpenCode with Homebrew on Linux
The homebrew-core opencode formula tracks OpenCode 2 and has a bottle for x86_64 Linux, so nothing compiles. The setup cost is Homebrew itself. On Rocky Linux 10 the build tools it requires took 4 minutes to install:
sudo dnf group install -y "Development Tools"
sudo dnf install -y procps-ng curl file git
Then the official installer, which finished in 27 seconds unattended, and the line that puts brew on your PATH for the current shell (the installer prints the version to add to ~/.bashrc):
NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
Check the formula, then install it. The OpenCode docs list their own tap, anomalyco/tap/opencode-v2, but the core formula is current, so you don’t need it:
brew info opencode
brew install opencode
The formula reported the current 2.x bottle and two runtime dependencies, and the install took 20 seconds:
==> opencode: stable 2.2.0 (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
Homebrew’s shellenv puts /home/linuxbrew/.linuxbrew/bin ahead of /usr/local/bin, so on our Rocky box the brew copy silently shadowed the npm copy still installed there. Both reported the same version that day, which is exactly why nobody notices until one falls behind. Run which -a opencode after installing, and remove whichever copy you didn’t mean to keep.
Choose npm or the Standalone Binary
Both routes run the identical 246,831,280-byte binary, so the choice is only about how it arrives and what it costs. The npm package is a thin wrapper whose postinstall script picks the right native build and hard-links it into place, and npm installs every x64 Linux variant as optional dependencies. On the Debian VM that looked like this:
du -sh /usr/local/lib/node_modules/@opencode/cli/node_modules/@opencode/*
Four builds of about 235 MB each, of which your machine runs one:
236M /usr/local/lib/node_modules/@opencode/cli/node_modules/@opencode/cli-linux-x64
236M /usr/local/lib/node_modules/@opencode/cli/node_modules/@opencode/cli-linux-x64-baseline
235M /usr/local/lib/node_modules/@opencode/cli/node_modules/@opencode/cli-linux-x64-baseline-musl
235M /usr/local/lib/node_modules/@opencode/cli/node_modules/@opencode/cli-linux-x64-musl
Pick npm when Node is already part of the machine and you want OpenCode pinned in the same place as your other global tools, or when a team manages versions through package.json. Pick the curl binary for servers, containers and CI images, because it needs no runtime, no root and a quarter of the disk. Whichever you choose, don’t install with --ignore-scripts (common in hardened npm configs): the install reports success, but the binary is a 229-byte stub that fails on first run. The error and its fix are in the troubleshooting section below.
Upgrade OpenCode 1 to OpenCode 2
OpenCode 1 has an opencode upgrade --major flag that installs OpenCode 2 with the method it detects and removes OpenCode 1. Whether that works depends on that method, so check how your copy got there before running it:
which -a opencode
opencode --version
Back up the config directory first, because the swap is one way and the backup is the only record of how OpenCode 1 behaved:
cp -a ~/.config/opencode ~/opencode-v1-config.bak
Then use the line for your install method:
- curl install (
~/.opencode/bin):opencode upgrade --major, no sudo - npm install under the distro’s Node (
/usr/local/bin):sudo /usr/local/bin/opencode upgrade --major - npm install under nvm:
opencode upgrade --major, no sudo (not tested here) - Arch
opencode-binfrom the AUR: remove it, thensudo pacman -S opencode
On the Debian VM the curl copy upgraded without a prompt in 10.8 seconds: it printed “Using method: curl” and then “OpenCode 2 installed”. The npm copy on Rocky is the one that trips people up. Run as your user, it detects npm, tries to write into the root-owned /usr/local/lib/node_modules, and fails with nothing but an exit code. Running the same binary through sudo, by full path because the RHEL sudo secure_path leaves out /usr/local/bin, completed the job and left only @opencode/cli in the global tree:

Notice that the failed run still prints “Done”, and the shell got exit status 0 back, so a script that checks $? will think the upgrade worked. Always check opencode --version afterwards. Sessions that existed before the first OpenCode 2 start come across: the OpenCode 1 session we created before upgrading was listed by opencode session list under OpenCode 2 on both VMs. Sessions OpenCode 1 creates after that point don’t, which matters if you roll back and forth (see the last section).
Check What Changed in Your Config
OpenCode 2 reads an OpenCode 1 opencode.json as it is and translates it in memory, so most setups keep working with no edits. The file on disk is not rewritten. To see what OpenCode 2 made of yours, ask it:
opencode debug config
Our Rocky config used the OpenCode 1 keys permission with a bash map, an agent with prompt and maxSteps, and an MCP server with enabled: false. Every one came back under its OpenCode 2 name:
"permissions": [
{
"action": "shell",
"resource": "git push *",
"effect": "deny"
},
{
"action": "shell",
"resource": "*",
"effect": "ask"
},
{
"action": "edit",
"resource": "*",
"effect": "allow"
}
],
"agents": {
"reviewer": {
"system": "Review for correctness.",
"mode": "subagent",
"steps": 5
}
},
"mcp": {
"servers": {
"fs": {
"type": "local",
"command": [
"npx",
"-y",
"@modelcontextprotocol/server-filesystem",
"/tmp"
],
"disabled": true
}
}
}
The translation keeps your rules in the order you wrote them, and that order decides the result, because the last matching rule wins. Our config listed the git push * deny before the * ask, so when we asked the agent to run git push origin main, OpenCode 2 treated it as “ask”, not “deny”. We reinstalled OpenCode 1 on Debian with the same file, and it also raised a permission request for the push instead of denying it, so the upgrade did not change anything: that deny had never applied. Fix the order while you’re in the file, broad rules first and specific denies last, then test one denied command with opencode run. The full rename list is in the official migration guide and our OpenCode 2 config walkthrough. These are the changes that broke or surprised us:
- OpenCode 1 plugins stop loading, with only a log warning
- the first TUI start copies
tui.jsonsettings into a new globalcli.json - language servers configured under
lspno longer run - only
AGENTS.mdloads as project instructions - a background server keeps running between commands
The CLAUDE.md change is easy to misread. With only a CLAUDE.md in the project, the free model on Rocky still answered with its instruction, but only because it searched the directory and read the file as a tool call. That is not the same as loading it: a model may skip the search. With both files present, AGENTS.md won. Copy your rules into AGENTS.md rather than relying on the model to go looking.
Fix OpenCode 2 Migration Errors
Every error below came up on the lab VMs while moving between versions and package managers. Most of them don’t stop OpenCode from starting, which is why they’re easy to miss.
Upgrade failed for npm (exit code 243)
You get this from opencode upgrade --major on an npm global install owned by root. npm cannot write to /usr/local/lib/node_modules as your user, and OpenCode reports only the exit code. 243 is 256 minus 13, and 13 is EACCES, the permission error, which fits a root-owned directory. Run it through sudo with the full path, as shown in the upgrade section, or use a Node install under nvm, which keeps global packages in your home directory. Never chown /usr/local/lib to your user to get around it.
npm error EEXIST: file already exists /usr/local/bin/opencode
You hit this when you run sudo npm install -g @opencode/cli while opencode-ai is still installed. Both packages claim the opencode command, so npm refuses after a full minute of downloading:
npm error code EEXIST
npm error path /usr/local/bin/opencode
npm error EEXIST: file already exists
npm error File exists: /usr/local/bin/opencode
npm error Remove the existing file and try again, or run npm
npm error with --force to overwrite files recklessly.
Skip --force, because it would leave the OpenCode 1 package installed with its command link taken over by the new one. Remove the old package, then install the new one:
sudo npm uninstall -g opencode-ai
sudo npm install -g @opencode/cli
On Rocky the uninstall took under a second and the clean install 23 seconds, after which opencode --version printed the v2 string.
Error: @opencode/cli’s postinstall script was not run
You installed with --ignore-scripts, or ignore-scripts=true is set in an .npmrc. npm reports “added 5 packages”, but the postinstall never linked the native binary, so the command is a stub:
Error: @opencode/cli's postinstall script was not run.
This occurs when installation scripts are disabled.
Run the package postinstall script or reinstall with scripts enabled.
Drop the flag (or the ignore-scripts line in .npmrc) and install again. It took 5 seconds because the platform packages were already downloaded:
sudo npm install -g @opencode/cli
Bun and pnpm block install scripts by default, which is why their install commands in the OpenCode docs carry --trust and --allow-build=@opencode/cli.
Could not detect the installation method. Pass –method to choose how to upgrade OpenCode
You ran opencode upgrade on a copy a distro package manager owns, such as the Arch extra package in /usr/bin. The --method choices are curl, npm, pnpm, bun, yarn, vp, brew and winget, with no pacman, so OpenCode cannot upgrade it and says so. Upgrade with the package manager instead (sudo pacman -Syu on Arch), and leave --method alone, because forcing curl installs a second copy rather than upgrading the first.
opencode-bin and opencode are in conflict
An AUR helper or pacman -U is trying to install the AUR opencode-bin package, which is OpenCode 1, over the extra package. Answering “y” would downgrade you to OpenCode 1. Answer “N”, remove opencode-bin from your helper’s list, and stay on the extra package. If you already have opencode-bin installed, remove it with sudo pacman -R opencode-bin and then install opencode from extra.
failed to load plugin: Plugin must export a default definition
The OpenCode 1 plugin in ~/.config/opencode/plugin/notify.js had fired on every idle session before the upgrade. Under OpenCode 2 nothing happened, and the only trace was a WARN line in ~/.local/share/opencode/log/:
level=WARN message="failed to load plugin" target=/home/josphat/.config/opencode/plugin/notify.js cause="Cause([Fail(PluginModule.LoadError: Plugin must export a default definition with an id and an effect or setup function. (cause: SchemaError(Missing key\n at [\"default\"])))])"
OpenCode 2 wants a default export with an id and a setup(ctx) function, and a local plugin is a directory, not a single file. We moved the old file aside and put a minimal port in ~/.config/opencode/plugins/notify/:
mkdir -p ~/.config/opencode/plugins/notify
mv ~/.config/opencode/plugin/notify.js ~/v1-notify.js.bak
vim ~/.config/opencode/plugins/notify/index.js
This is the exact file the Rocky VM loaded:
export default {
id: "notify",
async setup(ctx) {
console.error("notify plugin loaded in " + ctx.location.directory)
},
}
On the next opencode run the log showed “loading plugin” for plugins/notify followed by “watcher started”, with no failure. Hooks, tools and events all moved to new APIs on the ctx object, so a real plugin needs porting hook by hook using the plugin migration guide. Grep the log for “failed to load plugin” after every upgrade, because OpenCode itself won’t tell you.
Error: Server process 52772 is still running
OpenCode 2 runs a background server that outlives each command. Right after we swapped npm packages on Rocky, the first opencode debug config found that server unresponsive, logged “Background service is unresponsive; recovery cannot preserve persistent terminals”, and failed with this error. The log repeated that warning twice more over the next half minute, then OpenCode replaced the stale server and the same command worked, with a new PID in ~/.local/state/opencode/service.json. So retry before you debug anything. OpenCode 2 also writes a service.json into ~/.config/opencode/, and both copies hold the background server’s password. If you keep that directory in a dotfiles repository, add service.json to its .gitignore.
Pin a Version or Roll Back to OpenCode 1
The v2 script reads a VERSION variable, so pinning a known-good release when a new one misbehaves is a one-liner. We pinned an earlier 2.x build on Debian, which took 9 seconds, and opencode upgrade then moved it back to the latest release in 10:
export OPENCODE_PIN=2.1.4 # pin: rollback target, pick from https://www.npmjs.com/package/@opencode/cli?activeTab=versions
curl -fsSL https://opencode.ai/v2/install | VERSION="${OPENCODE_PIN}" bash -s -- --no-modify-path
opencode --version
Passing --no-modify-path stops the script from touching ~/.bashrc again, and the script also accepts the same version as --version. On npm the equivalent is sudo npm install -g @opencode/[email protected], which took 28 seconds on Debian, and @latest moves it forward again. On Arch you’d reinstall the older package from /var/cache/pacman/pkg/ with pacman -U and add IgnorePkg = opencode to /etc/pacman.conf so the next -Syu doesn’t undo it. We didn’t run the Arch rollback, and it only works if that version was installed on the machine before.
Going all the way back to OpenCode 1 works too, with one catch. The old install URL still serves OpenCode 1:
curl -fsSL https://opencode.ai/install | bash
Run over a v2 install on Debian, it replaced the binary (the opencode2 shim stayed behind) and OpenCode 1 started cleanly, both before and after OpenCode 2 had written to the database. Both majors share one ~/.local/share/opencode/opencode.db, but OpenCode 2 keeps its sessions in new tables (session_v2, session_message) next to the old ones. OpenCode 2 imported the sessions that existed when it first started, but after that each version lists only its own: sessions we created in OpenCode 1 after the upgrade never showed up in OpenCode 2, and OpenCode 2’s session never showed up in OpenCode 1. Roll back only for a short test, and if you do, keep using your OpenCode 1 config backup rather than a converted file, because the migration guide warns against pointing OpenCode 1 at config rewritten in OpenCode 2 shapes.
Once you are on OpenCode 2 for good, our OpenCode CLI cheat sheet has the session, run and upgrade commands on one page. To point the new install at other models, see OpenCode with an OpenAI-compatible API key, and for how it compares on real infrastructure tasks, our AI coding agent test for DevOps.