OpenCode
Open-source multi-provider CLI coding agent with external launch verification and Keychain-backed secret injection.
Last updated
Cloud API Go1. Installation
Prerequisites
- macOS 13+ or Linux (kernel 5.13+)
- Go 1.21+ (or install pre-built binary)
- API key for at least one provider (Anthropic, OpenAI, Google, xAI, Groq)
Install OpenCode
brew install opencode
Install the preferred stack
brew tap nvk/tap
brew install nvk/tap/agent-bondage
brew install nvk/tap/envchain-xtra
brew install nono
Verify
opencode --version
bondage --help
nono --version
envchain --version
2. nono Profile
OpenCode stores config in ~/.opencode.json (written with 0644 permissions — world-readable). In the preferred setup, bondage verifies the OpenCode target first, envchain-xtra injects the needed provider keys, and nono limits what the process can touch beyond the project directory.
What the sandbox allows
| Resource | Access | Why |
|---|---|---|
| Current working directory | Read + Write | Project files |
~/.opencode.json | Read | Config (no secrets if using envchain) |
| API endpoints | Network | Provider API calls |
/dev/tty, /dev/null | Read + Write | Terminal I/O |
/dev/urandom | Read | Crypto randomness |
{env:VARIABLE_NAME} interpolation in config files. This does not work. The source code treats apiKey as a literal string — it sends the text {env:ANTHROPIC_API_KEY} to the API as the bearer token. Omit the providers section entirely and let OpenCode read keys from env vars via os.Getenv().
3. envchain-xtra
OpenCode supports multiple providers. Store each API key in the same envchain-xtra namespace so the chosen provider credentials are injected into one process tree without living in plaintext config files.
Store your API keys
# Store all provider keys under one namespace
envchain --set opencode ANTHROPIC_API_KEY
envchain --set opencode OPENAI_API_KEY
Provider environment variable names
| Provider | Env Var |
|---|---|
| Anthropic | ANTHROPIC_API_KEY |
| OpenAI | OPENAI_API_KEY |
| Google / Gemini | GOOGLE_GENERATIVE_AI_API_KEY |
| xAI / Grok | XAI_API_KEY |
| Groq | GROQ_API_KEY |
GOOGLE_GENERATIVE_AI_API_KEY, not GEMINI_API_KEY. Using the wrong name silently fails.
Minimal config file (no secrets)
# ~/.opencode.json — safe to commit to dotfiles
{
"provider": "anthropic",
"model": "claude-sonnet-4-6"
}
4. bondage Wrapper
Add this thin wrapper to your shell config:
opencode() {
bondage exec opencode ~/.config/bondage/bondage.conf -- "$@"
}
Sample stack snippets
Assuming your shared [global] block already exists in ~/.config/bondage/bondage.conf, this is a script-backed OpenCode shape to adapt:
# ~/.config/bondage/bondage.conf
[profile "opencode"]
namespace = opencode
use_envchain = true
use_nono = true
nono_profile = opencode
touch_policy = prompt
target_kind = script
target = /absolute/path/to/opencode/bin/opencode
target_fp = sha256:replace-me
interpreter = /absolute/path/to/node
interpreter_fp = sha256:replace-me
package_root = /absolute/path/to/opencode
package_tree_fp = sha256:replace-me
nono_allow_cwd = true
nono_allow_file = /dev/tty
nono_allow_file = /dev/null
nono_read_file = /dev/urandom
{
"extends": "opencode",
"meta": {
"name": "opencode",
"description": "OpenCode with project-only access and envchain-backed provider keys"
},
"policy": {
"add_deny_access": ["/Volumes"]
},
"workdir": {
"access": "readwrite"
}
}
How it works
The sequence is:
- The shell wrapper passes only the profile name and your arguments
bondageverifies the exact OpenCode targetenvchain-xtraresolves Keychain lookups before the sandbox appliesnonoapplies Seatbelt/Landlock restrictions- OpenCode reads provider keys from env vars — no plaintext file access needed
Reload your shell:
source ~/.zshrc
5. Verification
Test the full chain
bondage verify opencode ~/.config/bondage/bondage.conf
bondage chain opencode ~/.config/bondage/bondage.conf -- --help
opencode --help
Confirm no plaintext keys
# Should return nothing
grep -r "sk-ant\|sk-proj" ~/.opencode* 2>/dev/null
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| "Invalid authentication credentials" | Config file has literal {env:...} string |
Remove the providers section from config |
| Wrong provider used | Config overrides env var defaults | Keep config minimal — just provider and model |
| "Operation not permitted" | nono blocking a path | Check nono why for denied access |