TekOnline

How We Set Up OpenCode to Be Actually Useful — A Practical Guide

We have been trying out OpenCode, a terminal-based AI coding assistant, across a few different projects — .NET web apps, Docker Compose stacks, and some embedded firmware work. Out of the box, it works. But the difference between the default experience and a tuned setup is night and day.

This article covers the settings, tweaks, and project-level conventions that made the biggest difference for us.

Global Config: The Essentials

OpenCode’s global config lives at ~/.config/opencode/opencode.json. If the file does not exist, create it. The schema reference is at https://opencode.ai/config.json.

Here is the config we landed on after a few weeks of using it:

{
  "$schema": "https://opencode.ai/config.json",
  "logLevel": "INFO",
  "share": "disabled",
  "autoupdate": "notify",
  "snapshot": true,
  "formatter": true,
  "lsp": true,
  "instructions": [
    "AGENTS.md",
    "CLAUDE.md",
    "CONTRIBUTING.md",
    "README.md"
  ],
  "compaction": {
    "auto": true,
    "tail_turns": 5,
    "reserved": 4000
  },
  "tool_output": {
    "max_lines": 4000,
    "max_bytes": 102400
  },
  "experimental": {
    "mcp_timeout": 15000,
    "continue_loop_on_deny": true
  },
  "permission": {
    "edit": "ask",
    "bash": "ask",
    "external_directory": {
      "~/.config/**": "allow",
      "~/AppData/Local/Temp/**": "allow",
      "*": "ask"
    }
  }
}

Let me walk through the settings that actually matter.

formatter and lsp

"formatter": true,
"lsp": true

These two are the biggest single win.

With formatter: true, OpenCode runs Prettier, dotnet-format, or whatever formatter your project uses on any file it touches. The code it writes comes out looking like yours, not like an AI guessed your style.

With lsp: true, it hooks into your language server. It sees compiler errors, warnings, and lint results in real time. When it writes code that does not compile, it knows immediately and fixes it in the same turn. Without this, it can write broken code and not realise until you tell it.

These are both off by default. Turn them on.

instructions

"instructions": [
  "AGENTS.md",
  "CLAUDE.md",
  "CONTRIBUTING.md",
  "README.md"
]

This tells OpenCode to automatically read these files from whichever project directory you launch it from. If the file exists, its contents get injected into every session as system instructions.

This is how it learns your project conventions without you having to repeat yourself. More on what to put in these files later.

compaction

"compaction": {
  "auto": true,
  "tail_turns": 5,
  "reserved": 4000
}

When a conversation gets long enough to hit the model’s context limit, OpenCode compresses the history. It summarises old messages and keeps recent ones verbatim.

The default keeps only 2 recent turns intact. We bumped tail_turns to 5 because we found the model would sometimes lose track of what it was doing mid-task when the default summarised too aggressively.

reserved: 4000 leaves a 4k token buffer so compaction runs before the context is completely full, giving the model room to respond without an overflow error.

tool_output

"tool_output": {
  "max_lines": 4000,
  "max_bytes": 102400
}

The defaults truncate tool output at 2000 lines or 50KB. For us, that was too aggressive. Build logs, test output, and git diff results were getting cut off mid-read. Doubling it to 4000 lines and 100KB fixed most of the truncation without making things unreadable.

experimental.continue_loop_on_deny

"experimental": {
  "mcp_timeout": 15000,
  "continue_loop_on_deny": true
}

By default, if you deny a tool call — say, it wants to run a command and you say no — OpenCode stops the entire agent loop. With continue_loop_on_deny: true, it just moves on to the next thing. Less interruptive. Recommended.

mcp_timeout: 15000 is 15 seconds. Default is 5, which is too tight for some MCP servers to initialise.

permissions

"permission": {
  "edit": "ask",
  "bash": "ask",
  "external_directory": {
    "~/.config/**": "allow",
    "~/AppData/Local/Temp/**": "allow",
    "*": "ask"
  }
}

Leaving permissions at their defaults is fine. We tightened a few things. edit: "ask" means it asks before modifying files — we prefer the safety. bash: "ask" means it asks before running commands. The external_directory block lets it freely access config and temp directories while still prompting for anything else outside the project.

Per-Project Config: AGENTS.md

The global config gets you 80% of the way there. The last 20% is project-specific context, and the best tool for that is an AGENTS.md file at the root of your project.

OpenCode reads this automatically if you have "AGENTS.md" in your global instructions array.

Here is an example from one of our .NET + Docker projects:

# Project Overview

ASP.NET Core 8 web API with SQL Server backend, deployed via Docker Compose.
Drone CI handles builds and deployments on push to main.

## Tech Stack
- .NET 8, ASP.NET Core
- Entity Framework Core with SQL Server
- Docker & Docker Compose
- Nginx reverse proxy
- Drone CI for CI/CD

## Project Structure
- `src/` — .NET solution and projects
- `docker/` — Dockerfiles and compose files
- `.drone.yml` — CI pipeline

## Conventions
- Use file-scoped namespaces
- Controllers go in `src/WeHireIt.Api/Controllers/`
- Services go in `src/WeHireIt.Core/Services/`
- DTOs in `src/WeHireIt.Core/DTOs/`
- Database migrations via EF Core CLI, not the package manager console
- Async all the way down — all controller actions and service methods are async

## Testing
- xUnit with Moq for unit tests
- Run tests: `dotnet test`
- Integration tests use Testcontainers for SQL Server

## Linting
- Run `dotnet format --verify-no-changes` to check formatting
- Run `dotnet build` to check compilation

The key things to include:

  • Tech stack. Model, framework, database, infrastructure.
  • Project structure. What goes where. Especially important for larger repos.
  • Conventions. Naming patterns, file organisation, architectural rules.
  • Test commands. The exact commands to run tests, lint, and build.
  • Constraints. Things it should not do — avoid certain libraries, stick to existing patterns, never commit without asking.

Without an AGENTS.md, OpenCode guesses your tech stack. With one, it knows. The difference in code quality and relevance is obvious.

Custom Agents for Specific Jobs

The built-in agents — buildplanexploregeneral — cover most tasks. But we added a custom subagent for code review:

Create .opencode/agents/review.md:

---
description: Strict code reviewer. Use ONLY when asked to review code or PRs.
mode: subagent
permission:
  edit: deny
  bash: deny
---

You are a strict code reviewer. When given code, check for:

1. Null reference risks — every nullable type must be checked
2. Missing async/await — no .Result or .Wait() calls
3. SQL injection — all queries must be parameterised
4. Hardcoded secrets or connection strings
5. Missing error handling on external calls
6. Inconsistent naming with the project conventions

Report each issue with the file, line, severity, and a suggested fix.
Do NOT modify any files. This is read-only review.

Then invoke it with /review or by mentioning @review in chat.

Custom agents save you from typing the same instructions over and over.

MCP Servers: Worth the Setup

MCP (Model Context Protocol) servers give OpenCode access to external tools. The one we use most is the filesystem server, which lets it search and navigate outside the current project when needed.

"mcp": {
  "filesystem": {
    "type": "local",
    "command": ["npx", "-y", "@anthropic-ai/mcp-server-filesystem", "/path/to/docs"],
    "enabled": true
  }
}

If you work with databases, the PostgreSQL or SQLite MCP servers let OpenCode inspect schemas and run read-only queries directly.

What We Learned

  1. Turn on formatter and lsp. They are off by default and they are the two highest-impact settings. Do it now.
  2. Write an AGENTS.md for every project. It takes 5 minutes and pays back in every session. Be specific about conventions, not generic.
  3. Bump compaction.tail_turns to 5. The default of 2 is too aggressive and the model loses context mid-task.
  4. Set instructions to auto-load context files. AGENTS.mdCLAUDE.mdCONTRIBUTING.md, and README.md cover most projects.
  5. Double the tool output limits. Truncated build logs and test output waste more time than longer tool responses.
  6. Enable continue_loop_on_deny. Without it, denying a tool call kills the agent loop. Annoying.
  7. Use project-level opencode.json for overrides. Keep the global config general. Put project-specific models, MCP servers, and agents in each repo’s local config.
  8. Restart after config changes. Config is loaded at startup and is not hot-reloaded. Save, quit, restart.

Sources


Posted

in

by

Tags:

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *