Files
kugetsu/skills/opencode-worktree/SKILL.md
shokollm 63b89eed6d feat: add opencode-worktree skill for isolated sessions
- Adds skills/opencode-worktree/ with SKILL.md and opencode-worktree.sh
- Creates unique git worktree per session (e.g. session-20260327-a1b2c3-refactor-auth)
- Cleans up stale worktrees on every launch
- Branch always based on main
- User can source directly or copy to PATH
2026-03-27 12:59:41 +00:00

5.5 KiB

opencode-worktree

Isolated OpenCode sessions via git worktrees.

Overview

Each OpenCode session gets its own git worktree with a unique branch. This prevents:

  • Clashes with parallel sessions on the same repo
  • Accidental overwrites from multiple agents
  • Confusion from work-in-progress across contexts

Prerequisites

  • Git
  • opencode installed and configured

Installation

. skills/opencode-worktree/opencode-worktree.sh

Option 2: Copy to PATH

cp skills/opencode-worktree/opencode-worktree.sh ~/.local/bin/opencode-worktree
chmod +x ~/.local/bin/opencode-worktree

Usage

Create new session

. opencode-worktree.sh                    # session-20260327-a1b2c3
. opencode-worktree.sh refactor-auth      # session-20260327-a1b2c3-refactor-auth

Cleanup

. opencode-worktree.sh --cleanup          # remove all session-* worktrees
. opencode-worktree.sh --cleanup <name>   # remove specific worktree

How It Works

  1. Cleanup - On every launch, removes all stale session-* worktrees and their branches
  2. Create - Creates new worktree based on main with unique name: session-{timestamp}-{random6}[-{purpose}]
  3. Launch - Changes into worktree and launches opencode
  4. Exit - When opencode exits, you return to your original directory (worktree remains for review)

Example Workflow

# Start session for refactoring auth
. opencode-worktree.sh refactor-auth

# ... do work in opencode ...

# Exit opencode (worktree with your changes still exists)
# Later, resume or cleanup
. opencode-worktree.sh --cleanup session-20260327-a1b2c3-refactor-auth

Script Source

#!/bin/bash
# opencode-worktree - Isolated OpenCode sessions via git worktrees

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
WORKTREE_BASE="$PWD/.git/worktrees"
PURPOSE=""
CLEANUP_ONLY=false
CLEANUP_NAME=""

usage() {
    cat <<EOF
Usage: $(basename "$0") [purpose] [--cleanup [name]]

Create isolated OpenCode sessions via git worktrees.

Arguments:
  purpose            Purpose string for the session (optional)
  --cleanup          Remove all session-* worktrees
  --cleanup <name>   Remove specific worktree by name

Examples:
  $(basename "$0")                    # session-20260327-a1b2c3
  $(basename "$0") refactor-auth      # session-20260327-a1b2c3-refactor-auth
  $(basename "$0") --cleanup          # remove all session-* worktrees
EOF
}

parse_args() {
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --cleanup)
                CLEANUP_ONLY=true
                if [[ $# -gt 1 && ! "$2" =~ ^-- ]]; then
                    CLEANUP_NAME="$2"
                    shift
                fi
                ;;
            -h|--help)
                usage
                exit 0
                ;;
            *)
                if [[ -z "$PURPOSE" ]]; then
                    PURPOSE="$1"
                fi
                ;;
        esac
        shift
    done
}

cleanup_stale() {
    if [[ ! -d "$WORKTREE_BASE" ]]; then
        return
    fi

    for wt in "$WORKTREE_BASE"/session-*; do
        [[ -d "$wt" ]] || continue

        branch=$(git -C "$wt" rev-parse --abbrev-ref HEAD 2>/dev/null) || continue

        echo "Removing stale worktree: $(basename "$wt")"
        git worktree remove "$wt" --force 2>/dev/null || true
        if [[ -n "$branch" && "$branch" != "HEAD" ]]; then
            git branch -D "$branch" 2>/dev/null || true
        fi
    done
}

cleanup_single() {
    local name="$1"
    local wt_path="$WORKTREE_BASE/$name"

    if [[ ! -d "$wt_path" ]]; then
        echo "Worktree '$name' not found"
        return
    fi

    branch=$(git -C "$wt_path" rev-parse --abbrev-ref HEAD 2>/dev/null) || branch=""

    echo "Removing worktree: $name"
    git worktree remove "$wt_path" --force 2>/dev/null || true
    if [[ -n "$branch" && "$branch" != "HEAD" ]]; then
        git branch -D "$branch" 2>/dev/null || true
    fi
}

create_worktree() {
    local timestamp=$(date +%Y%m%d-%H%M%S)
    local random=$(head -c 3 /dev/urandom | xxd -p | head -c 6)
    local worktree_name="session-${timestamp}-${random}"
    local branch_name="$worktree_name"

    if [[ -n "$PURPOSE" ]]; then
        worktree_name="${worktree_name}-${PURPOSE}"
        branch_name="$worktree_name"
    fi

    local worktree_path="$WORKTREE_BASE/$worktree_name"

    # Cleanup any existing with same name
    if [[ -d "$worktree_path" ]]; then
        echo "Removing existing worktree: $worktree_name"
        git worktree remove "$worktree_path" --force 2>/dev/null || true
    fi

    # Ensure main exists and is up to date
    if ! git show-ref --quiet refs/heads/main 2>/dev/null; then
        echo "Error: 'main' branch does not exist"
        exit 1
    fi

    # Create worktree from main
    echo "Creating worktree: $worktree_name"
    git worktree add -b "$branch_name" "$worktree_path" main

    # Launch opencode in worktree
    echo "Entering worktree and launching opencode..."
    cd "$worktree_path"
    exec opencode
}

main() {
    # Verify we're in a git repo
    if ! git rev-parse --is-inside-work-tree 2>/dev/null; then
        echo "Error: Must be run inside a git repository"
        exit 1
    fi

    if [[ "$CLEANUP_ONLY" == true ]]; then
        if [[ -n "$CLEANUP_NAME" ]]; then
            cleanup_single "$CLEANUP_NAME"
        else
            cleanup_stale
        fi
        exit 0
    fi

    cleanup_stale
    create_worktree
}

parse_args "$@"
main