Git worktree management for opencode — completely agent-driven, safe, and permission-aware.
The main point: the agent drives the whole worktree lifecycle itself — it decides when to branch off, works in the worktree with full file access, and folds the work back into your target branch, all as tool calls inside its own session. You never spawn terminals, open extra opencode instances, or juggle sessions; guards make destructive accidents hard.
- Four agent tools —
worktree_create,worktree_merge,worktree_remove, andworktree_list(rediscover existing worktrees with their branch and clean/uncommitted status, e.g. after compaction). Worktrees live under${XDG_STATE_HOME:-~/.local/state}/opencode/worktrees/<repo>-<branch>. - Safety-first git — fast-forward-only merges by default (or the repository's own
merge.ffconfig via themergeStrategyoption), branch deletion only after a verified merge, refusal to remove worktrees with uncommitted changes, never--force. - Permission-aware — two
permissionModestrategies forexternal_directoryaccess inside the managed worktrees:"all-worktrees"(default) — at plugin init theconfighook adds a single static allow for the entire worktrees parent directory (${XDG_STATE_HOME:-~/.local/state}/opencode/worktrees/**). There are no per-worktree permission entries that come and go — every managed worktree sits under that one prefix rule, so the agent can read and edit inside worktrees without prompts, even under a catch-all deny. Apermission.askhook backstop allows any path inside the same roots should a prompt still occur."pedantic"— no static allow is added. Instead the plugin watches everyexternal_directorypermission request and transparently approves it — without prompting the user — only when the requested paths are inside a currently active plugin worktree (derived from git at ask time). Access is revoked automatically once a worktree is merged or removed. This keepsexternal_directoryatask(or stricter except a deny) for everything else, so unrelated directories outside the managed worktrees still prompt normally. Notes: requires opencode ≥ 1.18 (the permission-reply API the plugin uses); an explicit configdenyrule always short-circuits before the plugin sees the request, so pedantic mode cannot rescue a catch-all deny — useall-worktreesfor that; and transparent approval applies to interactive sessions —opencode runresolves permission asks itself (approves with--auto, auto-rejects otherwise) before the plugin can.
- Single-session — the agent keeps working in your session; worktrees are just directories it edits. A TUI status bar tracks the active worktrees (e.g.
config-fix (3)); clicking it lists their absolute paths with clipboard copy. - Agent directive — a system-prompt hook tells agents to prefer these tools over raw git and explains what raw git skips.
- No terminal spawning — it does not open new terminals or start separate opencode sessions per worktree; the agent does all of it in the session you are already in.
- No auto-commit, no force — it never commits, merges, or deletes anything you did not ask for; it refuses and explains instead of force-cleaning.
- No lifecycle hooks or file syncing — beyond an optional
.opencode/copy prompt, it does not syncnode_modules, run hooks, or manage dependencies. - No multiplexer integration — no tmux/cmux workflows.
Compared to opencode-worktree
Both wrap git worktree for agents, but differ in who does the driving:
| Aspect | opencode-worktree | this plugin |
|---|---|---|
| Driver | New terminal + session per worktree | Agent tool calls, same session |
| Delete | Snapshot auto-commit, then --force |
Refuses uncommitted, never force |
| Merge | Manual | worktree_merge (configurable strategy) |
| Extras | File sync, hooks, tmux/cmux | Permissions, TUI bar, nix git |
| Location | ~/.local/share/opencode/worktree/… |
${XDG_STATE_HOME}/opencode/worktrees/… |
Use theirs if you want each worktree to be a self-contained terminal session that cleans up after itself. Use this one if you want the agent to manage the whole worktree lifecycle on its own — several concurrent worktrees, merged and cleaned up when it decides the work is done — with you staying in one session.
The plugin ships as two entry points — opencode-worktree-plugin (server) and opencode-worktree-plugin/tui (TUI status bar). Register both in your opencode config.
preferNixDevelop(defaultfalse) — run git vianix develop -c gitwhen aflake.nixis present.mergeStrategy(default"ff-only") — howworktree_mergefolds a worktree branch back:"ff-only"— fast-forward only, no merge commits; if the branches have diverged, rebase the worktree branch onto the target first."repo-config"— follow the respective git repository'smerge.ffconfiguration: unset/truefast-forwards when possible and creates a merge commit otherwise,falsealways creates a merge commit,onlyrequires a fast-forward. Merge commits for a target branch that is not checked out are built ref-only via git plumbing (merge-tree/commit-tree/update-ref), so no working copy is touched; conflicted working-copy merges are rolled back withgit merge --abort.
permissionMode(default"all-worktrees") — howexternal_directoryaccess inside the managed worktrees is granted:"all-worktrees"— a staticexternal_directoryallow for the entire worktrees parent directory (see above). Works even under a catch-all deny, but also covers stale directories under the root that are no longer active worktrees."pedantic"— no static allow; the plugin transparently auto-approvesexternal_directoryrequests (no user prompts) only when every requested path lies inside a currently active plugin worktree, and approves persistently ("always") only when the suggested persistent patterns are worktree-scoped as well. Anything else — including directories that merely sit under the worktrees root but are not live worktrees — falls back to the normal prompt/ask behavior.
To opt in, pass it where the plugin is registered:
// opencode.json
{
"plugin": [
["opencode-worktree-plugin", { "mergeStrategy": "repo-config", "permissionMode": "pedantic" }],
],
}nix develop -c npm ci
nix develop -c npm test # unit + integration tests
nix develop -c npm run build # dist/index.js + dist/tui.jsThe devshell ships a pinned, unwrapped opencode binary (currently v1.18.25), so
integration tests run against the same version the plugin targets — and any
global config a custom wrapper might inject stays out. Outside nix, opencode
on PATH must be the real binary, not a wrapper.
Integration tests also need a git-ignored .env in the repo root:
OPENAI_MODEL=<model id>
OPENAI_URL=<openai-compatible base url>
# API key: a file on disk (no clear-text secret) or the key itself.
# A directly set OPENAI_API_KEY (env or .env) wins over the file.
OPENAI_API_KEY_FILE=/path/to/key/fileMIT