owt — OpenCode Worktree Manager
Manage git worktrees across parallel OpenCode sessions. Create named worktrees, open them in new terminals, view their status, and commit — all without switching branches or losing uncommitted work.
What it does
- Named worktrees — attach human-readable names to branches (e.g. "Fix login bug" instead of
feature/WIN-123)
- Parallel sessions — unlimited features in progress simultaneously, zero conflicts
- Dirty status — see which worktrees have uncommitted changes at a glance
- Quick access —
owt <name> opens a worktree in a new terminal window
- In-place git ops —
owt diff, owt status, owt log, owt merge, owt commit without cd
- Co-author strip —
owt hook removes AI-injected Co-authored-by: from every commit
- Shared
node_modules — symlinked from main repo; no duplicate installs
Release channels
@latest (v2.x) — stable, single-repo worktree management
@beta (v3.0.0) — adds multi-repo workspace support; install with npx @nano-step/opencode-worktree-plugin@beta install or npm install -g @nano-step/opencode-worktree-plugin@beta
If you don't need workspace mode, @latest is the safer pick.
How OWT works
OWT has three layers that work together. Each layer can be used independently.
┌─────────────────────────────────────────────────────────────┐
│ You (human) │
├──────────────┬──────────────────────┬───────────────────────┤
│ OpenCode │ OpenCode session │ Terminal │
│ main session│ (worktree) │ │
│ │ │ │
│ /init-worktree ──→ createworktree ──┼──→ owt <name> │
│ /list-worktrees ─→ listworktrees │ owt status/diff/ │
│ /open-worktree │ log/merge/commit │
├──────────────┴──────────────────────┴───────────────────────┤
│ Slash Commands (command/*.md) │
│ Parse arguments, call plugin tools │
├─────────────────────────────────────────────────────────────┤
│ Plugin (worktree-plugin.js) │
│ 3 tools: createworktree / deleteworktree / │
│ listworktrees │
│ + system prompt injection │
│ + state management (worktree-sessions.json) │
├─────────────────────────────────────────────────────────────┤
│ Shell Script (owt.sh) │
│ Terminal manager: list, open, status, diff, │
│ log, merge, commit, hook │
├─────────────────────────────────────────────────────────────┤
│ git worktree (built-in git) │
│ The actual isolation mechanism │
└─────────────────────────────────────────────────────────────┘
Layer 1 — Plugin (worktree-plugin.js)
The plugin registers three tools that OpenCode's AI agent can call:
createworktree | Runs git worktree add, symlinks node_modules, saves session state |
deleteworktree | Checks for uncommitted changes, runs git worktree remove, cleans up state |
listworktrees | Combines git worktree list with saved display names and dirty status |
System prompt injection: When OpenCode starts a session, the plugin injects context into the AI's system prompt:
- Inside a worktree → injects cleanup rules ("tell user to run deleteworktree when done")
- In the main repo → injects availability hint ("use createworktree for parallel tasks")
This is how the AI "knows" it's inside a worktree without being told.
State management: Display names, session IDs, and creation timestamps are stored in .opencode/worktree-sessions.json. This file is read by both the plugin and the shell script. It is not committed to git.
Layer 2 — Slash commands (command/*.md)
Markdown files that define /init-worktree, /list-worktrees, and /open-worktree. These are prompt templates that:
- Parse user arguments (branch name, display name, base branch)
- Call the plugin tools with the correct parameters
- Format the output for the user
They are the user-facing interface inside OpenCode sessions. The AI reads the markdown instructions and executes them.
Layer 3 — Shell script (owt.sh)
A standalone bash script for use outside OpenCode — in any terminal. It reads the same worktree-sessions.json state file to resolve display names, and calls git directly for all operations.
Key capabilities not available through the plugin:
- Terminal detection: Auto-detects Ghostty, iTerm, Terminal.app, WezTerm, Kitty, Alacritty, tmux — opens a new window with
opencode --cwd
- Fuzzy matching:
owt fix matches "Fix login bug" by partial name or branch
- In-place git ops:
owt diff, owt log, owt status without cd-ing into the worktree
- Co-author hook: Strips AI-injected
Co-authored-by: lines from commit messages
Data flow: creating a worktree
/init-worktree feature/WIN-123 name:"Fix login bug"
│
▼
Slash command parses args
│
▼
createworktree(branch="feature/WIN-123", name="Fix login bug")
│
├─ Branch exists locally? → git worktree add <path> <branch>
├─ Branch exists remote? → git fetch + git worktree add
└─ New branch? → git worktree add <path> -b <branch> <base>
│
▼
Symlink node_modules from main repo → worktree
│
▼
Save to .opencode/worktree-sessions.json:
{ "sessions": { "<sessionId>": { "branch": "...", "name": "...", "worktreePath": "..." } } }
│
▼
User opens: opencode --cwd ".opencode/worktrees/feature/WIN-123"
│
▼
Plugin detects worktree directory → injects WORKTREE_RULES into system prompt
│
▼
AI agent operates in isolated branch, full source checkout
Data flow: listing and opening
owt owt fix-login
│ │
▼ ▼
git worktree list Fuzzy-match "fix-login" against
+ display names and branch names
worktree-sessions.json │
│ ▼
▼ Resolve to single worktree path
Display all with │
✓/⚠ dirty status ▼
Detect terminal emulator
│
▼
Open new window:
opencode --cwd <worktree-path>
Quick start
Two paths depending on whether you want the owt CLI permanently.
Path A — npx (zero-install)
One command copies everything into ~/.config/opencode/:
npx @nano-step/opencode-worktree-plugin install
npx @nano-step/opencode-worktree-plugin@beta install
This copies the slash commands into ~/.config/opencode/command/ and the plugin into ~/.config/opencode/plugins/. No package is left installed on your machine.
Then restart OpenCode. Plugins and commands are loaded at startup — nothing takes effect until you do.
Note: npx runs the installer temporarily. The owt CLI is not added to your PATH. To test the install worked, open OpenCode and run /init-worktree. If you want owt on your PATH permanently, use Path B instead.
Path B — Global install (gives you owt CLI)
npm install -g @nano-step/opencode-worktree-plugin
npm install -g @nano-step/opencode-worktree-plugin@beta
owt-setup install
npm install -g puts owt and owt-setup on your PATH. owt-setup install copies the slash commands and plugin into ~/.config/opencode/ (same files as npx).
Then restart OpenCode.
Optionally, strip the AI co-author line from commits:
owt hook --global
Verify everything is working:
owt help
Troubleshooting: If owt help works but OpenCode doesn't recognize /init-worktree, you haven't restarted OpenCode yet.
Workspace mode (multiple repos)
Available in @beta (v3.0.0+). Install with npx @nano-step/opencode-worktree-plugin@beta install.
If you open OpenCode at a folder containing multiple git repos (not itself a git repo), the plugin automatically detects and works with all of them.
~/projects/
├── frontend/ ← .git
├── backend/ ← .git
└── shared-lib/ ← .git
opencode --cwd ~/projects
Create a worktree in a specific repo
/init-worktree feature/new-dashboard repo:"frontend" name:"Dashboard redesign"
List all repos and worktrees
/list-worktrees
Shows all repos, even those with no active worktrees.
How it works
- The plugin scans immediate children of the workspace directory for
.git folders
- Symlinks, hidden directories, and
node_modules are skipped
- Discovery caps at 50 repos
- Each repo's worktrees are stored inside that repo's
.opencode/worktrees/ directory (same as single-repo mode)
- The system prompt tells the AI which repos are available
Single-repo backward compatibility
If you open OpenCode inside a git repo (the existing workflow), everything works exactly as before. The repo parameter is optional and ignored in single-repo mode.
Install (step by step)
Step 1 — Install
Option A (npx, no permanent install):
npx @nano-step/opencode-worktree-plugin install
Downloads the package, copies config files into ~/.config/opencode/, then exits. Nothing stays on your PATH.
Option B (global, gives you owt CLI):
npm install -g @nano-step/opencode-worktree-plugin
owt-setup install
npm install -g puts owt and owt-setup on your PATH permanently. owt-setup install copies the slash commands into ~/.config/opencode/command/ and the plugin into ~/.config/opencode/plugins/. Requires Node.js >= 18.
Troubleshooting: If owt is not found after global install, your npm global bin directory may not be in your PATH. Run npm bin -g to find it.
Step 2 — Configure OpenCode
Restart OpenCode. This is the most common setup gotcha. Plugins and slash commands are loaded once at startup, so changes don't take effect until you restart.
After restarting:
- Test with
/init-worktree in OpenCode (works for both paths)
- Or run
owt help in your terminal (global install only)
Per-project plugin loading (alternative to global config): Instead of copying to ~/.config/opencode/, you can load the plugin per project via opencode.json:
{
"plugin": ["@nano-step/opencode-worktree-plugin"]
}
OpenCode resolves the package from your project's node_modules. Useful when different projects need different plugin versions.
Step 3 — Strip AI co-author from commits (optional)
If you use OpenCode to commit, it injects Co-authored-by: Sisyphus into every commit message. To strip it globally:
owt hook --global
This installs a commit-msg git hook at ~/.config/git/hooks/commit-msg and sets git config --global core.hooksPath. One-time setup, applies to all repos on your machine.
Note: git commit --no-verify bypasses hooks including this one.
Migrating from v1.x or unscoped package
If you previously installed the plugin by cloning the repo and copying files manually, remove the old copy before switching to the npm-based install:
rm ~/.config/opencode/plugins/worktree-plugin.js
If you also switch to opencode.json plugin loading (the "plugin": [...] key), make sure the file-based copy above is gone first. Otherwise the plugin loads twice, which causes duplicate tools and confusing behavior.
If you were using the unscoped opencode-worktree-plugin package, update your opencode.json to reference the scoped package:
{
"plugin": ["@nano-step/opencode-worktree-plugin"]
}
Then follow the Quick start steps above.
Usage
List all worktrees
owt
Output:
Active worktrees:
⚠ Fix login bug [dirty (uncommitted changes)]
Branch : feature/WIN-123
Path : /project/.opencode/worktrees/feature/WIN-123
Open : opencode --cwd "/project/.opencode/worktrees/feature/WIN-123"
✓ pod-3 hotfix [clean]
Branch : release/pod-3
Path : /project/.opencode/worktrees/release/pod-3
Open : opencode --cwd "/project/.opencode/worktrees/release/pod-3"
Open a worktree in a new terminal
owt fix-login
owt WIN-123
owt pod
Spawns a new terminal window running opencode --cwd <worktree-path>. Supports: Ghostty, iTerm, Terminal.app, WezTerm, Kitty, Alacritty, tmux.
View git status
owt status
owt status fix-login
View changes (diff)
owt diff fix-login
owt diff fix-login -- src/auth.ts
View recent commits
owt log fix-login
Merge into another branch
owt merge fix-login
owt merge fix-login --into main
owt merge fix-login --into release/qa3
Guards against dirty state — will error if uncommitted changes exist.
Commit from main session (without cd)
owt commit fix-login -m "feat: fix login redirect"
Runs git add -A + git commit inside the worktree. Useful when you want to commit without opening a new terminal.
Guideline: If you are on main and need to commit changes that belong to another worktree, always use owt commit <name> -m "..." instead of switching branches. This keeps each branch's history clean and avoids accidentally committing feature work onto main.
Examples:
owt commit implement-a -m "feat: implement A"
owt commit fix-login -m "fix: login redirect"
owt commit pod-3 -m "chore: bump version"
owt matches by partial name or branch — implement-a matches feature/implement-A.
Install co-author strip hook
owt hook --global
owt hook --local
owt hook --worktree WIN-123
Inside OpenCode (slash commands)
/init-worktree <branch> [name:"..."] | Create a named worktree |
/list-worktrees | List all worktrees with status |
/open-worktree [name-or-branch] | Print opencode --cwd command |
/owt-current | Show current worktree name/branch/status |
Examples:
/init-worktree feature/WIN-123 name:"Fix login bug"
/init-worktree hotfix/crash name:"Critical crash" from:main
/list-worktrees
/open-worktree fix-login
/owt-current
How git worktrees work — FAQ
These are the most common questions when using worktrees for the first time.
Does each worktree have the full source code?
Yes. A worktree is not a symlink or a partial copy. It is a complete checkout of the branch. Every file in the project exists inside the worktree directory.
project/ ← full source, branch: main
project/.opencode/worktrees/
feature/WIN-123/ ← full source, branch: feature/WIN-123
release/pod-3/ ← full source, branch: release/pod-3
The only thing that is symlinked is node_modules (to avoid duplicate installs).
Do 4 parallel sessions write to the same files?
No. Each session operates in its own directory. Session A edits worktrees/feature/WIN-123/src/Button.tsx. Session B edits worktrees/release/pod-3/src/Button.tsx. These are different files on disk.
Git also enforces branch locking — the same branch cannot be checked out in two worktrees simultaneously. If you try, git returns an error.
If I commit in worktree A, does the main repo see it?
Yes, immediately. All worktrees share a single .git repository. A commit in worktree A goes into the branch of worktree A. From the main session you can run:
git log feature/WIN-123 --oneline
git diff main...feature/WIN-123
You do not need to cd into the worktree to inspect its commits.
Do uncommitted changes get lost when I switch to another worktree?
No. Uncommitted changes stay in their worktree directory indefinitely. There is nothing to stash. To "switch" to another worktree, just cd to it or open a new terminal. The changes in the previous worktree are untouched.
This is the main advantage over git checkout — you never need to stash.
Why can't I commit feature A from the main repo directory?
The main repo directory is on a different branch (e.g. main or release/qa3). Committing there puts the commit on that branch, not on feature/implement-A.
To commit feature A, you must be in worktrees/feature/implement-A/ — or use owt commit implement-A -m "...".
Can I run npm test inside a worktree?
Yes. Each worktree has the full project structure and node_modules symlinked from the main repo. Run npm test, npm run build, or npm run start directly inside the worktree directory.
To run multiple features simultaneously on different ports:
PORT=3001 npm run start
PORT=3002 npm run start
PORT=3003 npm run start
How do I check out a worktree branch in the main repo?
You do not need to. From the main repo, use git -C to run git commands in any worktree without cd:
git -C .opencode/worktrees/feature/WIN-123 diff
git -C .opencode/worktrees/feature/WIN-123 log --oneline -5
git -C .opencode/worktrees/feature/WIN-123 status
Or use owt diff WIN-123, owt log WIN-123, owt status WIN-123.
End-to-end workflow
1. Create worktrees for each task
In OpenCode (main session):
/init-worktree feature/implement-A name:"Feature A"
/init-worktree feature/implement-B name:"Feature B"
/init-worktree feature/implement-C name:"Feature C"
/init-worktree feature/implement-D name:"Feature D"
2. Open each in a new session
owt feature-a
owt feature-b
owt feature-c
owt feature-d
Now you have 4 OpenCode sessions, each on their own branch, working in parallel.
3. Check progress from main session
owt
owt status
owt log A
4. Review and commit
Option A — commit inside the worktree session:
git add .
git commit -m "feat: implement A"
Option B — commit from main session without cd:
owt commit feature-a -m "feat: implement A"
5. Merge when done
owt merge feature-a --into release/qa3
npm test
owt merge feature-b --into release/qa3
npm test
6. Cleanup
In OpenCode, call deleteworktree for each worktree, or:
git worktree remove .opencode/worktrees/feature/implement-A
Important notes
node_modules is symlinked
When a worktree is created, node_modules is symlinked from the main repo root. This means:
- No
npm install needed per worktree — saves disk and time
- If you install a new package in a worktree session, it installs into the main
node_modules and is immediately available in all other worktrees
- If the main
node_modules does not exist yet, the symlink is skipped silently; run npm install in the main repo first
git hooks are shared between worktrees
All worktrees of a repo share the same .git/hooks directory. Installing owt hook --local or owt hook --worktree affects all worktrees of that repo, not just one. This is a git design decision, not a limitation of owt.
For per-repo isolation, use --local. For machine-wide isolation, use --global.
--no-verify bypasses all hooks
Running git commit --no-verify skips the commit-msg hook entirely. Co-authored-by will not be stripped. Avoid --no-verify unless intentional.
Branch locking
Git will refuse to checkout the same branch in two worktrees at the same time:
fatal: 'feature/WIN-123' is already checked out at '/path/to/worktree'
This is intentional and protects you from editing the same branch in two places.
State file
Display names and session metadata are stored at <repo-root>/.opencode/worktree-sessions.json. This file is gitignored automatically. If display names stop showing, check that this file exists and is valid JSON.
File structure
plugins/
└── worktree-plugin.js OpenCode plugin — createworktree / deleteworktree / listworktrees
command/
├── init-worktree.md /init-worktree slash command
├── list-worktrees.md /list-worktrees slash command
├── open-worktree.md /open-worktree slash command
└── owt-current.md /owt-current slash command
scripts/
└── owt.sh Terminal manager script (v2)
Requirements
- OpenCode with plugin support (
@opencode-ai/plugin >= 1.4.6)
- git >= 2.5 (worktree support)
- Node.js or Bun (for plugin runtime)
- Python 3 (used by owt.sh for JSON parsing — pre-installed on macOS/Linux)
- macOS or Linux