gh) installed and authenticated (gh auth login)Enable the plugin in your global opencode config (opencode will install it on next run):
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["opencode-synced"],
}
opencode does not auto-update plugins. To update, modify the version number in your config file.
Run /sync-init to create a new sync repo:
my-opencode-config by default)Run /sync-link to connect to your existing sync repo:
my-opencode-config)If auto-detection fails, specify the repo name: /sync-link my-opencode-config
After linking, restart opencode to apply the synced settings.
Sync global opencode configuration across machines via a GitHub repo, with optional secrets support for private repos.
~/.config/opencode) and related directoriesopencode-synced.overrides.jsonc/sync-* commands and opencode_sync toolgh) installed and authenticated (gh auth login)Enable the plugin in your global opencode config (opencode will install it on next run):
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["opencode-synced"],
}
opencode does not auto-update plugins. To update, modify the version number in your config file.
Run /sync-init to create a new sync repo:
my-opencode-config by default)Run /sync-link to connect to your existing sync repo:
my-opencode-config)If auto-detection fails, specify the repo name: /sync-link my-opencode-config
After linking, restart opencode to apply the synced settings.
You can specify a custom repo name or use an organization:
/sync-init - Uses {your-username}/my-opencode-config/sync-init my-config - Uses {your-username}/my-config/sync-init my-org/team-config - Uses my-org/team-configCreate ~/.config/opencode/opencode-synced.jsonc:
{
"repo": {
"owner": "your-org",
"name": "opencode-config",
"branch": "main",
},
"includeSecrets": false,
"includeMcpSecrets": false,
"includeSessions": false,
"includePromptStash": false,
"includeModelFavorites": true,
"extraSecretPaths": [],
"extraConfigPaths": [],
}
~/.config/opencode/opencode.json and opencode.jsonc~/.config/opencode/AGENTS.md~/.config/opencode/agent/, command/, mode/, tool/, themes/, plugin/~/.local/state/opencode/model.json (model favorites)extraConfigPaths (allowlist, files or folders)Enable secrets with /sync-enable-secrets or set "includeSecrets": true:
~/.local/share/opencode/auth.json~/.local/share/opencode/mcp-auth.jsonextraSecretPaths (allowlist, files or folders)MCP API keys stored inside opencode.json(c) are not committed by default. To allow them
in a private repo, set "includeMcpSecrets": true (requires includeSecrets).
Sync your opencode sessions (conversation history from /sessions) across machines by setting "includeSessions": true. This requires includeSecrets to also be enabled since sessions may contain sensitive data.
{
"repo": { ... },
"includeSecrets": true,
"includeSessions": true
}
Synced session data:
~/.local/share/opencode/storage/session/ - Session files~/.local/share/opencode/storage/message/ - Message history~/.local/share/opencode/storage/part/ - Message parts~/.local/share/opencode/storage/session_diff/ - Session diffsSync your stashed prompts and prompt history across machines by setting "includePromptStash": true. This requires includeSecrets to also be enabled since prompts may contain sensitive data.
{
"repo": { ... },
"includeSecrets": true,
"includePromptStash": true
}
Synced prompt data:
~/.local/state/opencode/prompt-stash.jsonl - Stashed prompts~/.local/state/opencode/prompt-history.jsonl - Prompt historyCreate a local-only overrides file at:
~/.config/opencode/opencode-synced.overrides.jsonc
Overrides are merged into the runtime config and re-applied to opencode.json(c) after pull.
If your opencode.json(c) contains MCP secrets (for example mcp.*.headers or mcp.*.oauth.clientSecret), opencode-synced will automatically:
opencode-synced.overrides.jsonc (local-only).{env:...} placeholders.This keeps secrets out of the repo while preserving local behavior. On other machines, set the matching environment variables (or add local overrides).
If you want MCP secrets committed (private repos only), set "includeMcpSecrets": true alongside "includeSecrets": true.
Env var naming rules:
CONTEXT7_API_KEY), it is used directly.opencode_mcp_<SERVER>_<HEADER> (non-alphanumerics become _).opencode_mcp_<SERVER>_OAUTH_CLIENT_SECRET.| Command | Description |
|---|---|
/sync-init |
Create a new sync repo (first machine) |
/sync-link |
Link to existing sync repo (additional machines) |
/sync-status |
Show repo status and last sync times |
/sync-pull |
Fetch and apply remote config |
/sync-push |
Commit and push local changes |
/sync-enable-secrets |
Enable secrets sync (private repos only) |
/sync-resolve |
Auto-resolve uncommitted changes using AI |
Restart opencode to run the startup sync flow (pull remote, apply if changed, push local changes if needed).
Inspect the local repo directly:
cd ~/.local/share/opencode/opencode-synced/repo
git status
git log --oneline -5
If the sync repo has uncommitted changes, you can:
/sync-resolve to let AI analyze and decide whether to commit or discard the changescd ~/.local/share/opencode/opencode-synced/repo
git status
git pull --rebase
Then re-run /sync-pull or /sync-push.
Run this one-liner to remove the plugin from your config, delete local sync files, and delete the GitHub repository:
bun -e '
const fs = require("node:fs"), path = require("node:path"), os = require("node:os"), { spawnSync } = require("node:child_process");
const isWin = os.platform() === "win32", home = os.homedir();
const configDir = isWin ? path.join(process.env.APPDATA, "opencode") : path.join(home, ".config", "opencode");
const dataDir = isWin ? path.join(process.env.LOCALAPPDATA, "opencode") : path.join(home, ".local", "share", "opencode");
["opencode.json", "opencode.jsonc"].forEach(f => {
const p = path.join(configDir, f);
if (fs.existsSync(p)) {
const c = fs.readFileSync(p, "utf8"), u = c.replace(/"opencode-synced"\s*,?\s*/g, "").replace(/,\s*\]/g, "]");
if (c !== u) fs.writeFileSync(p, u);
}
});
const scp = path.join(configDir, "opencode-synced.jsonc");
if (fs.existsSync(scp)) {
try {
const c = JSON.parse(fs.readFileSync(scp, "utf8").replace(/\/\/.*/g, ""));
if (c.repo?.owner && c.repo?.name) {
const res = spawnSync("gh", ["repo", "delete", `${c.repo.owner}/${c.repo.name}`, "--yes"], { stdio: "inherit" });
if (res.status !== 0) console.log("\nNote: Repository delete failed. If it is a permission error, run: gh auth refresh -s delete_repo\n");
}
} catch (e) {}
}
[scp, path.join(configDir, "opencode-synced.overrides.jsonc"), path.join(dataDir, "sync-state.json"), path.join(dataDir, "opencode-synced")].forEach(p => {
if (fs.existsSync(p)) fs.rmSync(p, { recursive: true, force: true });
});
console.log("opencode-synced removed.");
'
"opencode-synced" from the plugin array in ~/.config/opencode/opencode.json (or .jsonc).rm ~/.config/opencode/opencode-synced.jsonc
rm ~/.local/share/opencode/sync-state.json
rm -rf ~/.local/share/opencode/opencode-synced
gh repo delete.bun run buildbun run testbun run lintTo test the same artifact that would be published, install from a packed tarball into opencode's cache:
mise run local-pack-test
Then set ~/.config/opencode/opencode.json to use:
{
"plugin": ["opencode-synced"]
}
Restart opencode to pick up the cached install.
I stumbled upon opencodesync while publishing this plugin.
Sign in to leave a comment.