OpenCode Setup for V2: Write opencode.jsonc, Connect a Provider, Order Permission Rules and Add MCP Servers

OpenCode setup for V2: write a global opencode.jsonc, connect a provider, order last-match-wins permission rules, add MCP servers and avoid V1 config traps.

Diagram of OpenCode V2 permission rules in load order for git push origin main: base policy allow, global git push ask and project git push deny all match, global rm and build-agent question rules do not, and the project deny wins as the last match. A global policies card runs afterwards and can only add a deny.Diagram of OpenCode V2 permission rules in load order for git push origin main: base policy allow, global git push ask and project git push deny all match, global rm and build-agent question rules do not, and the project deny wins as the last match. A global policies card runs afterwards and can only add a deny.
In this guide's worked config, three rules match git push origin main, and the project file's deny wins because it is last. Global policies run afterwards and can only add a deny, which is why sudo sits there. Original diagram based on OpenCode's V2 Permissions documentation, read Oct 30, 2026.

Most OpenCode setup trouble in version 2 starts with a file written for version 1. You paste an opencode.json from an older tutorial, the editor underlines half of it, git push runs without asking, and the MCP server you added never appears. This guide is for developers installing or reconfiguring OpenCode 2, in the terminal client or Desktop, who want one working provider, permission rules that stop risky commands, and one or two MCP servers, all in the shape V2 actually documents.

You will finish with four things: a global ~/.config/opencode/opencode.jsonc, a project override at .opencode/opencode.jsonc, a connected provider with a pinned default model, and a rollback you have rehearsed. The global rules ask before git push and before the agent reads or edits outside the project. The repository override turns that push prompt into a hard deny. One local and one remote MCP server connect, and a third stays parked until a project needs it.

Every key in the examples comes from the OpenCode V2 documentation pages listed under Sources. Treat the JSON as a worked example to adapt, not captured output. We did not run these files for this guide, and where the docs are silent the text says so.

The timing hook is small. Anomaly published OpenCode v2.2.4 on GitHub on Oct 30, 2026. It added persistent Desktop terminals, an opt-in "experimental": { "artifacts": true } setting and a guided Claude Agent setup. Earlier the same day, v2.2.3 introduced the Claude Agent provider itself. The configuration lessons below apply to any 2.x build that follows the current V2 docs. For the reasoning-variant side of model config, see our guide to validating OpenCode reasoning-variant overrides. The open-source coding agent roundup shows where OpenCode sits among its peers.

GitHub release page for OpenCode v2.2.4 in the anomalyco/opencode repository.
The v2.2.4 release page in Anomaly's anomalyco/opencode repository on GitHub, published Oct 30, 2026. This guide was checked against the V2 docs current at this release. Source: GitHub. · Original source

Before you start: version, test repo and credentials

Check these before you touch any file. Each one removes a failure that would otherwise look like a config bug.

  • [ ] OpenCode 2.x is the binary on your PATH. The V1 migration guide says V1 and V2 both use the opencode command and are no longer installed side by side by default. Remove a V1 install managed by a package manager first, because the V2 curl installer replaces the V1 binary. In Desktop, Settings › About has had one-click version copying since v2.2.3.
  • [ ] A disposable git repository. You will ask the agent to push and then reject the request, so use a repo where an accidental push costs nothing.
  • [ ] Credentials for one provider. That means an API key, a subscription sign-in (ChatGPT Plus or Pro, a Claude Code login or OpenCode Go) or a local runtime such as Ollama.
  • [ ] Write access to ~/.config/opencode/ and to the repository’s .opencode/ directory.
  • [ ] Node.js with npx. The local MCP example starts its server with npx -y.
  • [ ] A copy of your existing config. V1 and V2 read the same locations, so a file converted to V2-only shapes can confuse a V1 binary later.
# Back up the existing global config (either extension), then list project config files
mkdir -p ~/opencode-config-backup
cp ~/.config/opencode/opencode.json* ~/opencode-config-backup/ 2>/dev/null
ls opencode.json opencode.jsonc .opencode/opencode.json .opencode/opencode.jsonc 2>/dev/null

The ls line matters later. If it prints more than one project file, you already have the precedence ambiguity described in the next section.

Where OpenCode 2 reads configuration, and which file wins

V2 reads the same system, global and project locations as V1. It normalizes supported V1 fields and native V2 fields in memory and never rewrites the file. That helps a migration but makes debugging harder: an old key can keep working quietly while a different file overrides the new one you are editing.

Location Scope Precedence the docs state What goes here in this guide
/etc/opencode/opencode.json(c), /Library/Application Support/opencode/opencode.json(c), %ProgramData%\opencode\opencode.json(c) Machine (system) Highest: overrides global and project files Nothing, unless you administer the machine
macOS managed preferences (ai.opencode.managed.plist) Machine (MDM) Highest, alongside system config Nothing
~/.config/opencode/opencode.json(c) You, in every project Below project config Providers, default model, baseline rules, personal MCP servers, policies
<project>/opencode.json(c) One repository Above global Leave it absent
<project>/.opencode/opencode.json(c) One repository Above global. Its order relative to the root-level project file is not stated on the pages used here Repo-specific rules and MCP overrides
~/.config/opencode/cli.json Terminal client only Not part of this merge TUI preferences (replaces V1 tui.json)

Three consequences follow.

  1. Pick one project file. The docs list both <project>/opencode.jsonc and <project>/.opencode/opencode.jsonc, but the pages used here do not say which wins when both set the same key. This guide uses .opencode/opencode.jsonc because it sits next to the agents, commands and skills that already live in .opencode/. If ls found both, merge them into one file and delete the other.
  2. Admin restrictions belong in policies, not settings. The migration guide notes that overriding a setting cannot stop users from adding providers, MCP servers, agents or plugins. Enforced restrictions should be written as policies, which outrank global and project policies.
  3. Mixed formats are tolerated only at the top level. Supported V1 and native V2 fields may sit side by side at the top level. When both set the same canonical value, a valid V2 value wins regardless of key order. Inside an individual agent, provider, command or model, keep to one format. OpenCode does not infer mixed formats there, and it logs warnings for conflicting values.

Connect a provider and pin a default model

The V2 Providers page describes setup as two slash commands: connect an account, then select a model. Credentials you enter through /connect stay out of opencode.jsonc, which is exactly where you want them.

  1. Open OpenCode in the test repository and run /connect. Choose the provider and enter its credentials.
  2. Run /models and select a model. Note the exact ID it shows, in provider/model form.
  3. Paste that ID into the top-level "model" key of the global file in the next section. With a pinned default, new sessions start on the model you chose rather than whichever one the picker last remembered.
  4. For effort-style variants, use provider/model#variant. The docs give claude-agent/opus#high as an example.

Subscription routes differ only in step 1:

Route What you do in step 1 Where the credential lives Model ID shape
API key for a catalog provider /connect, pick the provider, paste the key OpenCode’s provider account, not the config file anthropic/<model>
ChatGPT Plus or Pro /connect, OpenAI, Sign in with ChatGPT The plan sign-in. Usage counts against the plan, so models show no cost openai/<model>
Claude Agent Install Claude Code 2.1.248 or later, run claude auth login, then /connect and select Claude Agent Claude Code’s own login claude-agent/<model>#<effort>
OpenCode Go Subscribe in the console, copy the API key, /connect as OpenCode Go OpenCode’s provider account As listed in /models
Custom OpenAI-compatible endpoint No /connect. Define it under providers An environment variable named in env <provider key>/<models key>

Know Claude Agent’s limits before you choose it. Claude Code must be installed on the same machine as the OpenCode server, and Claude Agent is not available in remote workspaces. Claude Code’s system prompt replaces OpenCode’s built-in prompt, sampling options such as temperature are not supported, and the model list comes from Claude Code. v2.2.4 adds a guided flow that copies claude auth login, installs Claude Code and checks the connection before model selection.

If a paid plan does not appear after you subscribe, reconnect rather than editing config. One user reported on X on Oct 26, 2026 that a Go subscription only started working after they reran setup and selected Go on the initial provider screen. They also said they could not find their API key in the console. That is one user’s report, but it fits the documented path: /connect, choose the product, then /models.

The custom provider in the global file below follows the shape documented on the Providers page. The providers object is keyed by the provider ID used in model references, so that block produces the model ID acme/qwen3-coder. Here is what each field does. env lists, in order, the environment variables that can supply the credential. package selects the runtime: @opencode/ai/providers/openai-compatible is for APIs that use the OpenAI request format. settings.baseURL is the endpoint. models maps OpenCode’s model IDs to display names and, if needed, to a different upstream modelID.

The variable must exist in the environment of the OpenCode server process, not only in your interactive shell. OpenCode 2 runs one background service for every client, and the docs’ error table says to set a missing variable on the OpenCode server. The same page uses opencode service set env NAME VALUE to set a server-side variable, with OPENCODE_CLAUDE_PATH as its example. Typing a real key on the command line also puts it in your shell history, so load it from a secret manager or an env file you control.

One v2.2.4 fix matters if your organization pushes provider settings from the OpenCode Console. User, project and remote configuration can now override provider names, limits, variants, settings and headers supplied by the Console.

Write the global opencode.jsonc

This is the complete worked file. Save it as ~/.config/opencode/opencode.jsonc. JSONC allows the comments and trailing commas the V2 docs use. Replace the model ID, the Acme endpoint and the MCP servers with your own.

// ~/.config/opencode/opencode.jsonc  (worked example built from the V2 docs)
{
  "$schema": "https://opencode.ai/config.json",

  // Default model for new sessions. Copy the exact ID from /models.
  "model": "anthropic/claude-sonnet-5-5",

  "providers": {
    // Custom OpenAI-compatible endpoint. The key stays in ACME_API_KEY.
    "acme": {
      "name": "Acme",
      "env": ["ACME_API_KEY"],
      "package": "@opencode/ai/providers/openai-compatible",
      "settings": {
        "baseURL": "https://llm.acme.example/v1",
      },
      "models": {
        "qwen3-coder": { "name": "Qwen 3 Coder" },
      },
    },
    // Catalog provider: override only what you change. Milliseconds.
    "anthropic": {
      "settings": {
        "headerTimeout": 600000,
      },
    },
  },

  // Appended after the base policy (allow everything, ask on .env reads).
  "permissions": [
    { "action": "external_directory", "resource": "*", "effect": "ask" },
    { "action": "shell", "resource": "git push *", "effect": "ask" },
    { "action": "shell", "resource": "git reset --hard *", "effect": "ask" },
    { "action": "shell", "resource": "rm *", "effect": "ask" },
  ],

  // Runs after rules and saved approvals. Can only turn allow/ask into deny.
  "policies": [
    { "action": "tool.use", "resource": "shell:sudo *", "effect": "deny" },
  ],

  "mcp": {
    "timeout": { "startup": 45000, "catalog": 30000, "execution": 600000 },
    "servers": {
      // Local server over stdio.
      "everything": {
        "type": "local",
        "command": ["npx", "-y", "@modelcontextprotocol/server-everything"],
        "environment": { "LOG_LEVEL": "info" },
      },
      // Remote server with a header credential, OAuth off, native tool list.
      "context7": {
        "type": "remote",
        "url": "https://mcp.context7.com/mcp",
        "oauth": false,
        "headers": { "CONTEXT7_API_KEY": "{env:CONTEXT7_API_KEY}" },
        "codemode": false,
      },
      // Parked: configured but not connected until a project re-enables it.
      "sentry": {
        "type": "remote",
        "url": "https://mcp.sentry.dev/mcp",
        "disabled": true,
      },
    },
  },
}

Why each block is there:

  1. $schema points editors at the published schema. Keep it for autocomplete, but read the troubleshooting row on schema errors before you change anything an editor flags.
  2. model pins the default. The ID must match a /models entry exactly. For a custom provider, use the key from the models map, not the modelID value.
  3. providers.acme is the documented custom-provider shape, with no key in the file. providers.anthropic shows that a catalog provider needs only the fields you change. Here headerTimeout raises the wait for response headers from the five-minute default to ten minutes.
  4. permissions is appended after OpenCode’s base policy, which allows every action and asks before reading .env files. The first rule brings back V1’s prompt for paths outside the project. The next three ask before pushes, hard resets and rm.
  5. policies blocks sudo in every project. A policy runs after rules and saved approvals and can only turn allow or ask into deny, so no repository file can turn sudo back on.
  6. mcp.timeout sets defaults for every server. Startup gets 45 seconds because the first npx run downloads a package. Listing tools keeps the default 30 seconds. Tool calls get ten minutes instead of the 12-hour default, so a stuck tool fails while you are still watching.
  7. mcp.servers holds one local server, one remote server that uses a header credential with OAuth off, and one remote server parked with disabled: true.

Order permission rules so the last match is the one you meant

V2 replaces V1’s per-tool permission map with one ordered array of { action, resource, effect } rules. The V2 Permissions page puts the rule in one sentence: OpenCode combines rules in order and uses the last match. When no rule matches, the effect is ask.

Diagram of OpenCode V2 permission rules in load order for git push origin main: base policy allow, global git push ask and project git push deny all match, global rm and build-agent question rules do not, and the project deny wins as the last match. A global policies card runs afterwards and can only add a deny.Diagram of OpenCode V2 permission rules in load order for git push origin main: base policy allow, global git push ask and project git push deny all match, global rm and build-agent question rules do not, and the project deny wins as the last match. A global policies card runs afterwards and can only add a deny.
In this guide's worked config, three rules match git push origin main, and the project file's deny wins because it is last. Global policies run afterwards and can only add a deny, which is why sudo sits there. Original diagram based on OpenCode's V2 Permissions documentation, read Oct 30, 2026.

OpenCode builds the combined list in layers. The base policy comes first, then lower-priority configuration before higher-priority configuration, and agent rules come last. The project file outranks the global file, so its rules land later in the list and win when several rules match. The diagram shows this for git push origin main in this guide’s setup: three rules match, and the project’s deny is the last of them.

Effects and wildcards take a minute to learn:

Element Values Behaviour to remember
effect allow, deny, ask ask waits for a decision from the client
* in action or resource Zero or more characters, including / Matches the whole normalized value, not a substring
? Exactly one character Useful for single-character variants
Shell pattern ending in * git status * Also matches the bare command git status
Several resources in one operation A patch touching many files, a compound shell command Any deny denies. Otherwise any ask asks. Otherwise the operation is allowed
Home paths ~, ~/, $HOME Expanded for external_directory, read and edit. Shell resources stay raw text

Action names are where V1 configs break. Use shell, not bash. Use subagent, not task. Use edit for edit, write and patch. MCP tools use <server>_<tool>, and external_directory covers paths outside the active location and its worktree. execute controls Code Mode. doom_loop and lsp are not current V2 Core actions.

Here is the most common ordering mistake next to its fix:

Version Rules, top to bottom git push origin main Why
Broken shell git push * ask, then shell * allow Runs without a prompt The broad allow is the last match
Fixed shell * allow, then shell git push * ask Prompts The specific ask is the last match
Stricter shell * ask, shell git status * allow, shell git diff * allow, shell git push * deny Blocked The docs’ own example: ask by default, allow inspection, deny pushes

Two behaviours make the rules stronger than they look. First, compound commands are split. The built-in scanner, which handles Bash, Zsh, POSIX sh and PowerShell, extracts every subcommand, so git status && git push still hits the push rule. A command with syntax it cannot safely analyse returns a scanner error instead of running. Second, the docs warn that shell runs with your user’s full filesystem, process and network authority, and that directory inference from command text is best effort. Prefer a narrow allowlist to patterns that try to list every dangerous command. For a repository you do not trust, the Stricter row is the better baseline.

When a rule resolves to ask, the client offers Allow once, Allow always and Reject. Allow always saves the tool’s proposed pattern as a durable allow rule scoped to the project. For shell, that pattern is a command prefix such as git status *. Saved approvals never override a configured deny, but they can quietly turn an ask into an allow, so never choose Allow always on a push prompt. Reject also rejects every other pending permission request in the session.

OpenCode V2 Permissions documentation page on opencode.ai, opening with the Permissions heading and its introduction to configuring permission rules.
OpenCode's V2 Permissions documentation, captured from opencode.ai. This page defines the ordered action, resource and effect rules, the last-match-wins behaviour and the external_directory default used throughout this guide. Source: OpenCode. · Original source

The external_directory change needs its own warning. V1 asked before touching paths outside the project, but V2 allows external_directory by default. Without a rule, reads and edits outside the location and worktree go ahead with no prompt. The first line of the global permissions array brings the V1 prompt back. You can add narrower rules after it, for example allowing reads but denying edits under ~/projects/reference/*.

If you run several agents and want one policy across them, our piece on restricted mode as fleet policy argues for writing these rules down once per machine. The Codex approval modes and sandbox guide shows how a different harness splits the same decision between a sandbox and approvals.

Add one local and one remote MCP server

The V2 MCP servers page offers two routes: the opencode mcp CLI or hand-written config. The CLI is quicker for a first connection. Config is needed for working directories, OAuth client settings, timeouts and parking a server. If you already saved the global file above, steps 1 and 2 are the CLI equivalent and you can skip them.

  1. Add the remote server for this project. opencode mcp add context7 --url https://mcp.context7.com/mcp writes it to the project configuration. Add --global to make it available in every project. For a header credential, quote the placeholder so your shell writes it literally.
  2. Add the local server. Everything after -- is the command, and OpenCode starts it over stdio. Pass environment variables with repeated --env flags.
  3. Check connection state with opencode mcp list. A connected server prints a line like ✓ context7 connected. A remote server that needs sign-in shows needs authentication. Fix that by opening OpenCode, running /mcps, selecting the server and signing in, or by running opencode mcp auth <name>.
  4. Move anything beyond the basics into config, as the global file does. Pass secrets through {env:NAME}. Shell expressions such as $NAME are not expanded inside JSON strings.
  5. Park a server with disabled: true. V2 has no enabled field. Disconnecting a server in /mcps does not change configuration, so it reconnects on the next start.
# CLI equivalents of the config above (commands from the V2 MCP servers page)
opencode mcp add context7 --url https://mcp.context7.com/mcp \
  --header 'CONTEXT7_API_KEY={env:CONTEXT7_API_KEY}'
opencode mcp add everything --env LOG_LEVEL=info -- npx -y @modelcontextprotocol/server-everything
opencode mcp list
opencode mcp auth sentry     # OAuth sign-in for a remote server
opencode mcp logout sentry   # remove stored OAuth credentials

Three V2 defaults are easy to miss:

Setting Default What it changes
codemode true Tools are grouped per server behind Code Mode (tools.context7.<tool>). false puts them on the provider’s native tool list
oauth On for remote servers OpenCode discovers the authorization server, signs in with PKCE, refreshes tokens and stores credentials outside project config. Set false only for servers that use headers alone
protocol legacy auto probes for the 2026-07-28 revision and falls back. Use it only for servers that need it, because probing a local legacy server can wait out the startup timeout

Tool names are normalized. OpenCode names each tool <server>_<tool> and replaces every character other than letters, numbers, _ and - with _. That name is the permission action, so { "action": "context7_*", "resource": "*", "effect": "deny" } hides every Context7 tool without disconnecting the server. If an agent’s permissions deny all of a server’s tools, or deny execute for a server that uses Code Mode, OpenCode also leaves that server’s instructions out of the system prompt.

MCP timeouts now come in three kinds, and the error you see depends on which one fired. The chart puts the defaults next to the provider-side HTTP limits, because a slow tool and a slow model look much the same from the chat.

Six stat tiles of OpenCode V2 default timeouts. MCP servers: startup 30 s, catalog 30 s, execution 12 h. Provider HTTP settings: headerTimeout 5 min, chunkTimeout 15 min, timeout none.Six stat tiles of OpenCode V2 default timeouts. MCP servers: startup 30 s, catalog 30 s, execution 12 h. Provider HTTP settings: headerTimeout 5 min, chunkTimeout 15 min, timeout none.
Which timer fired depends on the stage: MCP servers get 30 seconds to start and 30 seconds to list tools but 12 hours per call, while provider requests wait 5 minutes for headers and 15 minutes between streamed chunks, and the whole-request provider timeout has no default. Original chart based on OpenCode's V2 MCP servers and Providers documentation, read Oct 30, 2026.

If a server fails while listing tools, raise catalog for that server only. If it fails partway through a call, look at execution. If it never connects, the culprit is startup. A server’s own timeout object overrides the matching keys in mcp.timeout. If you keep a long list of servers, the MCP server inventory ritual is a good weekly habit. The Claude Code MCP setup guide is the equivalent walkthrough for that client.

Tighten one repository with .opencode/opencode.jsonc

The project file narrows the global baseline for one codebase. It is meant to be committed, so it holds no keys.

// <repo>/.opencode/opencode.jsonc  (commit this file; it holds no secrets)
{
  "$schema": "https://opencode.ai/config.json",
  "permissions": [
    { "action": "shell", "resource": "git push *", "effect": "deny" },
    { "action": "edit", "resource": "migrations/*", "effect": "ask" },
    { "action": "read", "resource": "secrets/*", "effect": "deny" },
    { "action": "sentry_*", "resource": "*", "effect": "ask" },
  ],
  "mcp": {
    "servers": {
      // Replaces the whole global "sentry" object, so disabled is omitted (false).
      "sentry": {
        "type": "remote",
        "url": "https://mcp.sentry.dev/mcp",
      },
    },
  },
  "agents": {
    "reviewer": {
      "description": "Review code without changing it",
      "mode": "subagent",
      "permissions": [
        { "action": "edit", "resource": "*", "effect": "deny" },
      ],
    },
  },
}

What each entry does in practice:

  1. git push * → deny replaces the global ask with a hard block in this repository. The agent can still commit, but only a human pushes.
  2. migrations/* → ask gates edits to one directory. Edit rules match the target path for edit, write and patch.
  3. secrets/* → deny blocks reads from a directory that should never reach a model.
  4. sentry_* → ask prompts before any Sentry MCP tool runs. Once /mcps shows the real tool names, add narrower rules after this line, such as a deny for a specific write tool.
  5. mcp.servers.sentry re-enables the server parked in the global file. A higher-precedence project config replaces the entire server object with the same name. That is why this entry repeats type and url and leaves out disabled, which defaults to false. Forgetting url in an override is a classic bug.
  6. agents.reviewer adds a subagent whose own rules deny every edit. Agent rules are appended after top-level rules rather than replacing them, and a custom subagent uses its own permissions, not a subset of its parent’s.

This setup has an asymmetry. A repository can loosen your global asks by adding allows later in the list. Only a global or Console-managed policy can stop that, which is why sudo sits in policies in the global file. Admins using managed policy should also note a v2.2.3 change. integration.use now covers integration accounts only. MCP servers, skills and plugins have their own mcp.use, skill.use and plugin.use actions, and a bare integration.use rule no longer applies to them.

Translate V1 keys you copied from old tutorials

V2 still accepts supported V1 configuration, so you do not have to convert everything at once. Convert each block when you touch it anyway: the current docs, examples and error messages all describe native V2. This table covers the renames from the migration guide that turn up most often in pasted configs.

V1 key or value V2 replacement Note
permission (object per tool) permissions (ordered array) Order now decides precedence
bash action shell Same commands, new name
task action subagent Resource is the target agent ID
write, patch actions edit One action for all three tools
tools.websearch: false { "action": "websearch", "resource": "*", "effect": "deny" } Tool switches become rules
mcp.<name> mcp.servers.<name> V2 does not put server names directly under mcp
enabled: true/false disabled: false/true Inverse meaning
MCP timeout: 30000 timeout: { catalog, execution } startup is separate
clientId, clientSecret, callbackPort, redirectUri client_id, client_secret, callback_port, redirect_uri OAuth fields use snake case
provider providers Keyed by provider ID
npm, api, options.apiKey package (AI SDK packages get the aisdk: prefix), settings.baseURL, settings.apiKey Options split into settings, headers and body
agent and mode maps agents prompt → system, disable → disabled, maxSteps → steps
Separate variant model#variant anthropic/claude-sonnet-5-5#high
command, subtask commands, subagent Markdown command files still work
snapshot, attachment, plugin snapshots, media, plugins V1 plugin code does not run in V2
skills: { paths, urls } skills: [ ... ] One ordered array
small_model agents.title.model Model for the built-in title agent
azure-cognitive-services, google-vertex-anthropic azure, google-vertex V2 rejects the retired IDs

Some V1 fields have no V2 behaviour and are ignored with a warning. They include logLevel (use OPENCODE_LOG_LEVEL when starting OpenCode), server, top-level subagent_depth (now experimental.subagent_depth), compaction.tail_turns, compaction.prune and an MCP entry that has enabled but no type. There are two more traps. V1 plugin implementations do not run in V2 at all. And an agent Markdown file that uses any of permissions, request, system or disabled is read entirely as V2, so V1 fields left in the same frontmatter are ignored. The migration guide also suggests asking OpenCode itself to convert the file while preserving its behaviour. If you do, check the diff against this table.

Verify the setup in seven checks

Run these in order after restarting OpenCode. The expected observations come from the documentation. They describe what a correct setup should produce, not output captured for this article.

# Do this Expected observation If not
1 opencode mcp list in the test repo everything and context7 connected. sentry listed, probably needing authentication because the project re-enabled it See the startup and catalog rows under troubleshooting
2 /mcps, select sentry and sign in, or run opencode mcp auth sentry sentry connected Check the URL in the project override
3 /models, then ask “Reply with one sentence naming your model.” Your pinned model is selected and answers. acme/qwen3-coder appears as Qwen 3 Coder if ACME_API_KEY reaches the server “Model unavailable” means the ID or provider is wrong
4 Prompt: “Run git status, then push this branch.” git status runs without a prompt. The project deny blocks the push A push prompt means the project file did not load, or its rules landed too early
5 Repeat check 4 in a repo with no override A permission request for the push. Choose Reject No prompt: look for a later broad allow or a saved approval
6 Prompt: “Read …/another-repo/README.md.” An external_directory approval request before the read No prompt: the first global rule is missing or overridden
7 Append the context7_* deny rule to the project rules, restart, ask for library docs No Context7 tools offered. Remove the rule afterwards Tools still offered: check the normalized server name

Finish with a secrets check before you commit the project file:

git status --short
git grep -nIE "apiKey|Bearer |sk-[A-Za-z0-9]" -- .opencode opencode.json opencode.jsonc

The status should list only .opencode/opencode.jsonc, and the grep should print nothing. The pattern is an illustrative starting point, so adapt it to your providers’ key formats. If anything matches, move the value into an environment variable referenced with {env:NAME}. Then rotate the key, because it may already be in history. Our coverage of the OpenCode breach that exposed stored provider API keys is a reminder that where a key lives is a security decision.

People reporting on the schema issue also used opencode debug config. They said it exited 0 and printed the normalized configuration for files the runtime accepted. The documentation pages used here do not cover that command, so treat it as a convenience rather than a guarantee.

Troubleshooting: symptom, cause, fix

Symptom Likely cause Fix Expected after the fix
Editor flags permissions, mcp.servers, providers or an array-shaped skills as invalid The schema at opencode.ai/config.json was generated from V1 shapes (issue #43748). SchemaStore applies it to opencode.json(c) by filename even without $schema Trust the runtime over the editor and reload the editor’s schema cache. Never add enabled to satisfy it Diagnostics only on genuine typos
A V1 key seems to do nothing The field is one V2 ignores with a warning, or the entry mixes V1 and V2 fields Convert the whole entry with the table above and check the log for warnings The setting takes effect
Shell command runs without prompting A broad allow sits after the specific rule, or an earlier Allow always saved a project-scoped allow Put specific rules last. Review and remove broad saved approvals The command prompts
Project rules seem ignored Two project files, or rules in the file that loses Keep only .opencode/opencode.jsonc Check 4 shows a deny
MCP server never connects startup exceeded, often during a first npx download Raise timeout.startup for that server connected in opencode mcp list
Server connects but lists no tools catalog exceeded Raise timeout.catalog for that server only Tools appear in /mcps
Tool call dies partway through execution limit reached Raise that server’s execution only if the tool is legitimately slow The call completes
Server shows needs authentication OAuth not completed /mcps or opencode mcp auth <name>. Use opencode mcp logout <name> to switch accounts connected
Project override broke a global server The override replaced the whole object and dropped url or type Repeat every required field, or use a different server name Server connects
“No model is available for session” No provider has finished setup Finish /connect, then pick a model in /models Session starts
“Model unavailable: provider/model” Wrong ID, inactive provider or disabled model For custom aliases, check the models key, not modelID Model is selectable
“NAME is required to resolve the provider endpoint” A ${NAME} placeholder in baseURL is unset on the server Set it for the OpenCode server process or write the full URL Provider initializes
Paid plan missing after subscribing The account is not connected to the new product Rerun /connect and choose the product Its models appear in /models
“Claude Code is not logged in” or “too old” Claude Agent prerequisites missing Run claude auth login, then connect again. Run claude update for an old version Claude Agent models listed

Be precise about the schema issue’s status. Issue #43748 was opened on Aug 21, 2026. On Sep 24 a commenter confirmed that the published schema still had no servers key on released v2.0.13, and on Oct 6 another said it was still unfixed. The issue was closed as completed via #55524 on Oct 14, 2026. If your editor still complains, it may be holding a cached copy. One commenter worked around the problem with a vendored, patched schema referenced by a relative $schema path until the published file caught up. Throughout, the runtime was the authority: reporters noted that it accepted the V2 fields the schema rejected. A September commenter on v2.0.13 also reported that the runtime dropped the singular V1 provider key with no diagnostic, even though the migration guide says supported V1 fields keep working. That report is one more reason to write providers natively.

Roll back cleanly

Rehearse this once now, while nothing depends on the new setup.

  1. Remove the project override. Rename it so OpenCode stops reading it: mv .opencode/opencode.jsonc .opencode/opencode.jsonc.off. If it is already committed, revert that commit instead. Some guides mention an OPENCODE_DISABLE_PROJECT_CONFIG variable, but it does not appear on the V2 pages used here, so this guide renames the file.
  2. Restore the global file from ~/opencode-config-backup/. If you had no previous file, delete the new one.
  3. Clear MCP sign-ins you no longer want with opencode mcp logout sentry. Stored OAuth credentials live outside project config, so deleting the config entry alone leaves them behind.
  4. Review saved approvals. They are durable allow rules scoped to the project, and the docs advise removing broad ones you no longer need.
  5. Restart OpenCode and rerun checks 1, 3 and 4. You should see the old behaviour: no project deny, and whatever model your restored file pins.
  6. If you are going back to a V1 binary, point it only at V1-shaped files. The migration guide warns against pointing V1 at configuration converted to native V2-only shapes.

What to add next

Once the baseline holds, add providers one at a time and repeat checks 3 and 4 after each one. If you serve local models, the LM Studio setup guide for Claude Code, OpenCode and Codex covers the server side. Keep experimental keys such as artifacts out of the baseline until you have a reason to turn them on, because the release notes mark them opt-in.

Sources

  1. Release v2.2.4, anomalyco/opencode on GitHub, Oct 30, 2026
  2. Providers, OpenCode V2 docs
  3. Permissions, OpenCode V2 docs
  4. MCP servers, OpenCode V2 docs
  5. Migrate from V1, OpenCode V2 docs
  6. Issue #43748: published schema rejects documented V2 fields, anomalyco/opencode
  7. Release v2.2.3, anomalyco/opencode on GitHub, Oct 30, 2026
  8. cyber-ipman (@Cyberipman) on X, Oct 26, 2026
YOU'RE THROUGH THIS ONE.

Keep connecting the dots.

Back to the library

Many assistants.
One place to remember.

Bring your AI sessions, context, and usage together with Automater.

Meet Automater