opencode-remote-ssh enables OpenCode to execute commands on remote Linux hosts that cannot run OpenCode directly. It works by:
- Installing a small Go program (stub) on the remote host
- Creating an SSH tunnel from your local machine to the remote
- Proxying all commands through this tunnel to the remote stub
The remote stub acts as a miniature OpenCode server - it handles workspace, session, permission, and shell operations just like OpenCode would locally.
The OpenCode plugin that runs on your local machine. It handles:
| Responsibility | Description |
|---|---|
| Provider Registry | Maintains your list of remote hosts organized by groups |
| Host Selection | Picks which remote to use based on labels, availability, and optional host aliases |
| Lease Management | Ensures only one workspace uses a host at a time |
| SSH Bootstrap | Uploads and starts the remote stub |
| Tunnel Management | Maintains the SSH port forwarding |
| Workspace Adaptor | Implements OpenCode's workspace interface |
A self-contained Go binary that runs on the remote host. It provides:
| Feature | Description |
|---|---|
| HTTP API | OpenCode-compatible endpoints for all operations |
| Authentication | Validates bearer token on every request |
| State Management | Tracks workspaces, sessions, and approvals |
| Permission Engine | Enforces path access controls |
| Event Stream | SSE events for real-time updates |
When you create a remote workspace:
┌─────────────────┐ ┌─────────────────┐
│ Local OpenCode│ │ Remote Host │
└────────┬────────┘ └────────┬────────┘
│ │
│ 1. Select provider/host │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ Plugin │ │ │
│ - Load config │ │ │
│ - Pick host │ │ │
└────────┬────────┘ └────────┬────────┘
│ │
│ 2. SSH connect + upload stub │
├─────────────────────────────────────▶│
│ │
│ 3. Start stub + create tunnel │
├─────────────────────────────────────▶│
│ ▼
│ ┌─────────────────┐
│ │ Go Stub │
│ │ - workspace │
│ │ - session │
│ │ - shell │
│ │ - permission │
│ └────────┬────────┘
│ │
│ 4. Return remote URL │
▼ │
┌─────────────────┐ │
│ OpenCode uses │◀─────────────────────────────┘
│ remote as │ All subsequent commands
│ workspace │ go through the tunnel
└─────────────────┘
On each remote host, the stub installs under ~/.opencode-remote-ssh/:
~/.opencode-remote-ssh/
├── bin/
│ └── opencode-remote-ssh-stub # The Go binary (your code runs here)
├── run/
│ ├── stub.token # Authentication token
│ └── stub.pid # Process ID (optional)
├── log/
│ └── stub.log # Runtime logs
├── state/
│ ├── workspaces/ # Workspace metadata
│ ├── sessions/ # Active session data
│ └── approvals/ # Persisted "always" permissions
└── version # Version marker
The stub implements a subset of OpenCode's API so OpenCode can treat it as a remote workspace:
| Endpoint | Purpose |
|---|---|
GET /global/health |
Is the stub running? |
GET /global/event |
Real-time updates |
GET/POST /experimental/workspace |
Create/manage workspaces |
POST /session |
Start a new session |
POST /session/{id}/shell |
Run a command |
GET /permission |
Check for path approvals |
POST /permission/{id}/reply |
Approve/deny path access |
The stub is designed with security in mind:
- Localhost only: Binds to
127.0.0.1- only accessible via the SSH tunnel - Token auth: Every request must include a valid bearer token
- Default deny: All file access is blocked until explicitly approved
- Symlink blocking: Prevents escaping to arbitrary paths
- Approval persistence: "Always" approvals are saved but workspace-specific
Two recent runtime behaviors are important when reasoning about the current system:
- Host aliases: a configured host can expose multiple friendly names through
aliases, while still resolving to the samessh.hosttarget. - Detached remote startup:
setup-host.shlaunches the stub in a separate session via Pythonsetsidsemantics so the process survives bootstrap SSH teardown more reliably on older Linux hosts.
We chose this approach because:
- No remote dependencies: The stub is a single static binary - no Go installation needed on the remote
- Permission alignment: Uses OpenCode's permission system instead of a custom one
- Provider-based: Supports managing multiple hosts with labels
- SSH-based: Leverages existing SSH infrastructure for transport and authentication