Workspace Pattern - Git Worktrees for Multi-Repo Sessions
⚠️ DEPRECATED: This document describes the nested worktree pattern which doesn’t work due to gitignore conflicts.
See: workspace-pattern-v2.md for the correct sibling worktree pattern.
Date: 2026-01-30
Context: How OpenCode sessions see multiple repositories
The Problem
OpenCode sessions are scoped to a single git repository. When working with multiple repos in workspace-root/repos/, OpenCode can’t see changes because:
repos/is gitignored in workspace-root- Each repo in
repos/is an independent git repository - OpenCode running at workspace-root level only sees workspace-root changes
The Solution: Git Worktrees
Use git worktrees to create isolated workspaces where OpenCode can see all necessary repositories.
Pattern Overview
workspace-root/ # Main git repository
├── repos/ # Gitignored - independent repos
│ ├── ai-dev/ # Standalone git repo
│ ├── domain-apis/ # Standalone git repo
│ └── k8s-lab/ # Standalone git repo
│
└── .builders/ # Builder workspaces
├── ai-dev-gateway-slack/ # Git worktree of workspace-root
│ ├── .git → worktree ref # Points to workspace-root git
│ ├── repos/ # Worktrees of actual repos
│ │ └── ai-dev/ # Worktree → repos/ai-dev
│ ├── Taskfile.yaml # From workspace-root
│ └── workspaces.yaml # From workspace-root
│ └── [OpenCode runs here] # ← Session sees everything!
│
└── ai-dev-gateway-backend/ # Parallel session, isolated worktree
├── .git → worktree ref
├── repos/
│ └── ai-dev/ # Separate worktree → repos/ai-dev
└── [OpenCode runs here] # ← Different session, same repo
How It Works
Step 1: Create Builder Workspace (Worktree)
# Create a git worktree of workspace-root
git worktree add .builders/ai-dev-gateway-slack -b builder/ai-dev-gateway-slack
# Result:
# - .builders/ai-dev-gateway-slack/ created
# - New branch: builder/ai-dev-gateway-slack
# - Workspace-root files are visible hereWhat this gives you:
- ✅ Isolated workspace
- ✅ All workspace-root files visible
- ✅ Changes tracked in git (separate branch)
- ✅ No conflicts with other worktrees
Step 2: Initialize Repositories Inside Builder
cd .builders/ai-dev-gateway-slack
task builder:init REPOS=ai-dev,domain-apis,k8s-labWhat builder:init does:
- Creates
repos/directory in builder workspace - For each repo, creates a git worktree:
git worktree add repos/ai-dev ../../repos/ai-dev/main - Copies configuration files (Taskfile.yaml, workspaces.yaml)
Result:
.builders/ai-dev-gateway-slack/
├── repos/
│ ├── ai-dev/ # Worktree → workspace-root/repos/ai-dev
│ ├── domain-apis/ # Worktree → workspace-root/repos/domain-apis
│ └── k8s-lab/ # Worktree → workspace-root/repos/k8s-lab
├── Taskfile.yaml
└── workspaces.yaml
Step 3: Start OpenCode in Builder Directory
opencode serve --session ai-dev-gateway-slack --project .OpenCode now sees:
- ✅ All workspace-root files (via builder worktree)
- ✅ All repo files in
repos/(via repo worktrees) - ✅ All changes are git-tracked
- ✅ Clean separation from other sessions
Parallel Sessions
Multiple OpenCode sessions can work on the same repository simultaneously by using different worktrees:
# Session 1: Slack team
git worktree add .builders/ai-dev-gateway-slack -b builder/slack
cd .builders/ai-dev-gateway-slack
task builder:init REPOS=ai-dev
opencode serve --session slack
# Session 2: Backend team
git worktree add .builders/ai-dev-gateway-backend -b builder/backend
cd .builders/ai-dev-gateway-backend
task builder:init REPOS=ai-dev
opencode serve --session backend
# Both work on ai-dev repo, but in isolated worktrees
# Changes don't conflict until mergedIsolation:
- Each session has its own git branch
- Each session has its own worktree of repos
- No file conflicts during development
- Integrate via git merge when ready
Integration with AI Dev Gateway
When a user runs /opencode start in Slack, the gateway:
1. Create Builder Workspace
async def create_builder_workspace(category: str, project: str):
builder_name = f"{category}-{project}"
branch_name = f"builder/{builder_name}"
# Create worktree of workspace-root
await run_command(
f"git worktree add .builders/{builder_name} -b {branch_name}"
)
return f".builders/{builder_name}"2. Initialize Repositories
async def initialize_repos(builder_path: str, repos: List[str]):
repos_arg = ",".join(repos)
# Run builder:init task
await run_command(
f"task builder:init REPOS={repos_arg}",
cwd=builder_path
)3. Start OpenCode Session
async def start_opencode_session(builder_path: str, session_id: str):
# Start OpenCode in builder directory
await run_command(
f"opencode serve --session {session_id} --project .",
cwd=builder_path
)Benefits
For Development
- Visibility: OpenCode sees all necessary files
- Isolation: Each session is independent
- Parallelism: Multiple sessions on same repo without conflicts
- Git-tracked: All changes tracked properly
For the Gateway
- Clean workspaces: Each project gets isolated environment
- Easy cleanup: Delete worktree when done
- State persistence: Git branches preserve work
- Testing isolation: Each deployment can have its own workspace
Cleanup
When a session is complete:
# Delete the worktree
git worktree remove .builders/ai-dev-gateway-slack
# Optionally delete the branch
git branch -d builder/ai-dev-gateway-slackGateway automation:
async def cleanup_builder(builder_name: str, keep_branch: bool = False):
# Remove worktree
await run_command(f"git worktree remove .builders/{builder_name}")
# Optionally remove branch
if not keep_branch:
await run_command(f"git branch -D builder/{builder_name}")Example: Complete Session Lifecycle
# User: /opencode start
# Form: category=ai-dev, project=gateway-slack, repos=ai-dev
# Gateway Step 1: Create builder workspace
$ git worktree add .builders/ai-dev-gateway-slack -b builder/ai-dev-gateway-slack
Preparing worktree (new branch 'builder/ai-dev-gateway-slack')
HEAD is now at 0756050 fix: Correct BMAD slash commands
# Gateway Step 2: Initialize repos
$ cd .builders/ai-dev-gateway-slack
$ task builder:init REPOS=ai-dev
📥 Cloning ai-dev...
✅ Workspace initialization complete!
# Gateway Step 3: Start OpenCode
$ opencode serve --session ai-dev-gateway-slack --project .
OpenCode server started on port 8080
# Developer works...
# Files changed in:
# - .builders/ai-dev-gateway-slack/repos/ai-dev/services/gateway/...
# When complete:
$ git worktree remove .builders/ai-dev-gateway-slack
$ git branch -d builder/ai-dev-gateway-slackVerification
To verify a builder workspace is set up correctly:
# 1. Check it's a worktree
$ cat .builders/ai-dev-gateway-slack/.git
gitdir: /Users/craig/src/workspace-root/.git/worktrees/ai-dev-gateway-slack
# 2. Check branch
$ cd .builders/ai-dev-gateway-slack
$ git branch
* builder/ai-dev-gateway-slack
main
# 3. Check repos are worktrees
$ cat repos/ai-dev/.git
gitdir: /Users/craig/src/workspace-root/repos/ai-dev/.git/worktrees/...
# 4. Verify OpenCode sees changes
$ opencode session list
# Should show session with visible repo filesCommon Issues
Issue: “fatal: invalid reference”
Cause: Branch name conflicts
Solution:
# Use unique branch names per session
git worktree add .builders/session-123 -b builder/session-123Issue: “OpenCode doesn’t see repo files”
Cause: OpenCode not started in builder directory
Solution:
# Always CD into builder directory first
cd .builders/ai-dev-gateway-slack
opencode serve --session <id>Issue: “Worktree already exists”
Cause: Previous session not cleaned up
Solution:
# Remove old worktree
git worktree remove .builders/old-session
git worktree pruneReferences
- Git Worktrees: https://git-scm.com/docs/git-worktree
- Codev builder pattern:
workspace-root/.builders/task-rMsV/ - Builder init task:
workspace-root/Taskfile.yaml:builder:init
Summary
The Pattern:
- Create builder workspace (worktree of workspace-root)
- Initialize repos inside builder (worktrees of actual repos)
- Start OpenCode in builder directory
- OpenCode sees everything!
Why it works:
- Worktrees are part of git tree (not gitignored)
- OpenCode scans git tree for changes
- All repositories visible in single workspace
- Clean isolation between parallel sessions
This pattern enables the multi-repo, multi-session development model that powers the AI Dev Gateway.