---
# System prepended metadata

title: Oh-My-OpenCode Setup

---

# Oh-My-OpenCode Setup

Brief overview of my sandboxed opencode setup with oh-my-opencode and reusing claude auth to get around repeated 429s and annoying flakiness.

I run each instance of this on a zellij session on a server which I connect to over SSH (and forward my gpg agent over for commit signing).

If I get a token issue in opencode I just quickly create a new shell, type claude and switch back to the original. TTL is ~24 hours and usually dies around 9am, so I don't have any reason to script this atm.

Full workflow involves zed remote sessions where I can quickly review and inspect changes when needed. I have one master zed session running locally with a terminal tab for each workstream (what can I say, I like the zed builtin terminal). This is open in my notes directory so I can easily switch between notes, todos and terminals and is where I spend the bulk of my time.
I have other zed sessions open in remote folders for some workstreams where I want to easily view files.
I also have a python http server set up and tunnel some ports via SSH so I can view web apps locally.

A lot of what I'm doing right now is simulation and research where this works really nicely.

## How the Claude Token Gets Reused

The chain is:

1. **Claude Code** authenticates via OAuth and stores credentials at `~/.claude/.credentials.json`:
```
{
"claudeAiOauth": {
"accessToken": "sk-ant-oat01-...",
"refreshToken": "sk-ant-ort01-...",
"expiresAt": 1774389809706,
"scopes": \[
"user:file\_upload",
"user:inference",
"user:mcp\_servers",
"user:profile",
"user:sessions:claude\_code"
\],
"subscriptionType": "team",
"rateLimitTier": "default\_claude\_max\_20x"
}
}
```

2. **OpenCode** loads the plugin `opencode-claude-auth@latest` (declared in `opencode.json`). This plugin reads the Claude OAuth tokens from that credentials file and uses them to authenticate Anthropic API calls — so OpenCode piggybacks on your existing Claude Code subscription without a separate API key.
3. Similarly, `opencode-antigravity-auth@latest` handles Google/Gemini auth via a separate credential flow stored in `~/.config/opencode/antigravity-accounts.json` (Google OAuth refresh token).
**In short**: You log into Claude Code once → `opencode-claude-auth` reads those tokens → OpenCode gets Anthropic API access for free (on your subscription). Same pattern for Gemini via Antigravity.

---

## OpenCode Config

## `~/.config/opencode/opencode.json` — declares plugins and providers:
```
{
"$schema": "https://opencode.ai/config.json",
"plugin": \[
"opencode-claude-auth@latest",
"oh-my-opencode@latest",
"opencode-antigravity-auth@latest"
\],
"provider": {
"anthropic": {
"options": {
"baseURL": "https://api.anthropic.com/v1"
}
}
}
}
```

The `plugin` array is the key — these three plugins are resolved and loaded at runtime by OpenCode itself (not via npm). The only npm dependency is the plugin SDK:
`~/.config/opencode/package.json`:
```
{
"dependencies": {
"@opencode-ai/plugin": "1.2.27"
}
}
```
Run `bun install` in `~/.config/opencode/` after creating this.

## Oh-My-OpenCode Config

`~/.config/opencode/oh-my-opencode.json` — the multi-agent orchestration brain:
```
{
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-opencode/dev/assets/oh-my-opencode.schema.json",
"agents": {
"sisyphus":          { "model": "anthropic/claude-opus-4-6",     "variant": "max" },
"hephaestus":        { "model": "openai/gpt-5.3-codex",         "variant": "medium" },
"oracle":            { "model": "openai/gpt-5.2",               "variant": "high" },
"explore":           { "model": "anthropic/claude-haiku-4-5" },
"multimodal-looker": { "model": "google/gemini-3-flash-preview", "variant": "medium" },
"prometheus":        { "model": "anthropic/claude-opus-4-6",     "variant": "max" },
"metis":             { "model": "anthropic/claude-opus-4-6",     "variant": "max" },
"momus":             { "model": "openai/gpt-5.2",               "variant": "medium" },
"atlas":             { "model": "anthropic/claude-sonnet-4-6" },
"sisyphus-junior":   { "model": "anthropic/claude-sonnet-4-6" },
"librarian":         { "model": "anthropic/claude-sonnet-4-6" }
},
"categories": {
"visual-engineering": { "model": "google/gemini-3.1-pro-preview", "variant": "high" },
"ultrabrain":         { "model": "openai/gpt-5.3-codex",         "variant": "xhigh" },
"deep":               { "model": "openai/gpt-5.3-codex",         "variant": "medium" },
"artistry":           { "model": "google/gemini-3.1-pro-preview", "variant": "high" },
"quick":              { "model": "anthropic/claude-haiku-4-5" },
"unspecified-low":    { "model": "anthropic/claude-sonnet-4-6" },
"unspecified-high":   { "model": "anthropic/claude-opus-4-6",     "variant": "max" },
"writing":            { "model": "google/gemini-3-flash-preview" }
}
}
```

### What this does

## **`agents`** — maps named agent roles to specific models. When the system prompt says "consult Oracle" or "fire explore agent", it dispatches to the model defined here. The primary agent (`sisyphus`) runs on Opus with max thinking. Cheap grunt work (`explore`) runs on Haiku. Consultation (`oracle`) runs on GPT-5.2.
**`categories`** — when `task(category="quick", ...)` is called, it spawns a `sisyphus-junior` agent using the model defined for that category. This lets you route different task types to the best-fit model: visual work → Gemini Pro, hard logic → GPT-5.3 Codex, trivial edits → Haiku.
**`variant`** — controls thinking budget. `max` = extended thinking, `high`/`medium`/`low` = progressively less thinking time, absent = no extended thinking.

## TL;DR Setup Steps

### 1. Install and authenticate Claude Code normally (gets you ~/.claude/.credentials.json)

claude login

### 2. Install OpenCode (https://opencode.ai)

### 3. Create ~/.config/opencode/opencode.json (see above)

### 4. Create ~/.config/opencode/package.json (see above)

### 5. Create ~/.config/opencode/oh-my-opencode.json (see above)

### 6. Install plugin SDK

cd ~/.config/opencode && bun install

### 7. Launch OpenCode — it picks up the plugins, reads Claude's OAuth tokens,

### and you're running the full multi-agent stack on your Claude subscription

opencode

# Sandboxing this

I sandbox each instance with a simple docker container and mount anything I need within it (Github read only PAT, SSH agent, GPG agent)

```fish
function opencode -d "Run OpenCode in a sandboxed Docker container"
    set -l image_name "opencode-sandbox"
    set -l workspace (pwd)
    set -l opencode_args

    for arg in $argv
        if test "$arg" = --rebuild
            echo "Rebuilding sandbox image..."
            docker rmi $image_name 2>/dev/null
	else
            set -a opencode_args $arg
        end
    end

    set workspace (realpath "$workspace")

    # Build UID-matched image from sandbox template (one-time)
    if not docker image inspect $image_name >/dev/null 2>&1
        echo "Building sandboxed OpenCode image (one-time)..."
        set -l host_uid (id -u)
        set -l host_gid (id -g)
        printf '%s\n' \
            "FROM docker/sandbox-templates:opencode" \
            "USER root" \
            "RUN usermod -u $host_uid agent && groupmod -g $host_gid agent && chown -R $host_uid:$host_gid /home/agent" \
	    "RUN mkdir -p /home/agent/.gnupg && chown $host_uid:$host_gid /home/agent/.gnupg && chmod 700 /home/agent/.gnupg" \
            "USER agent" \
        | docker build -t $image_name -
        or return 1
    end

    # Ensure host dirs exist (Docker creates missing mount sources as root-owned)
    mkdir -p "$HOME/.config/opencode" "$HOME/.local/share/opencode" \
             "$HOME/.local/state/opencode" "$HOME/.cache/opencode"

    # Core mounts: config, data, state, cache, workspace
    set -l docker_args \
        -it --rm \
        -v "$HOME/.config/opencode:/home/agent/.config/opencode" \
        -v "$HOME/.local/share/opencode:/home/agent/.local/share/opencode" \
        -v "$HOME/.local/state/opencode:/home/agent/.local/state/opencode" \
        -v "$HOME/.cache/opencode:/home/agent/.cache/opencode" \
        -v "$workspace:/home/agent/workspace"

    # Git config (read-only)
    if test -f "$HOME/.gitconfig"
        set -a docker_args -v "$HOME/.gitconfig:/home/agent/.gitconfig:ro"
    end

    # Forward GitHub token (read-only API access)
    if test -n "$GH_TOKEN"
        set -a docker_args -e "GH_TOKEN=$GH_TOKEN"
    end

    # Claude auth/config files
    if test -f "$HOME/.claude.json"
        set -a docker_args -v "$HOME/.claude.json:/home/agent/.claude.json"
    end
    if test -d "$HOME/.claude"
        set -a docker_args -v "$HOME/.claude:/home/agent/.claude"
    end

    # Forward SSH agent
    if test -n "$SSH_AUTH_SOCK"
        set -a docker_args \
            -v "$SSH_AUTH_SOCK:/tmp/ssh-agent.sock" \
            -e "SSH_AUTH_SOCK=/tmp/ssh-agent.sock"
    end

    # Forward GPG agent for commit signing (YubiKey)
    set -l gpg_sock "$HOME/.gnupg/S.gpg-agent"
    if test -S "$gpg_sock"
        set -a docker_args \
            -v "$HOME/.gnupg:/home/agent/.gnupg"
    end

    docker run $docker_args --entrypoint opencode $image_name $opencode_args
end
```

## SSH setup
Add the following to your `~/.ssh/config` for any SSH host where your opencode sessions are running to get yubikey signing and being able to view any web apps running on the remote (below port 56789)

```
    ...
    ControlMaster auto
    ControlPath ~/.ssh/control:%C
    ControlPersist 600
    LocalForward 56789 localhost:56789
    ...
    RemoteForward /home/donal/.gnupg/S.gpg-agent /Users/donal/.gnupg/S.gpg-agent.extra
```