Skip to content

Instantly share code, notes, and snippets.

@kevinold
Created March 17, 2026 01:34
Show Gist options
  • Select an option

  • Save kevinold/b7df076b728cd1c1dd50a21b68fed171 to your computer and use it in GitHub Desktop.

Select an option

Save kevinold/b7df076b728cd1c1dd50a21b68fed171 to your computer and use it in GitHub Desktop.
gh-worktree
#!/bin/bash
# gh-worktree - Create or delete a git worktree from a GitHub issue
# Requires: gh CLI (authenticated)
set -e
usage() {
echo "Usage: gh-worktree [-t type] [issue_number] [base_branch]"
echo " gh-worktree -d <issue_number>"
echo ""
echo "Options:"
echo " -t, --type TYPE Branch type: feat, fix, chore (default: auto-detect from labels)"
echo " -d, --delete Delete the worktree for the given issue"
echo " --no-claude Don't launch claude after creating worktree"
echo " -h, --help Show this help message"
echo ""
echo "Arguments:"
echo " issue_number GitHub issue number (optional — omit to create temp worktree)"
echo " base_branch Branch to base worktree on (default: staging)"
echo ""
echo "Examples:"
echo " gh-worktree 226 staging # Named worktree from issue #226 off staging"
echo " gh-worktree staging # Temp worktree off staging, name later"
echo " gh-worktree # Temp worktree off staging (default)"
echo ""
echo "Branch naming: {type}-{issue}-{short-description}"
echo " Example: feat-42-add-dark-mode"
}
DELETE=false
BRANCH_TYPE=""
LAUNCH_CLAUDE=true
while [[ $# -gt 0 ]]; do
case "$1" in
-d|--delete)
DELETE=true
shift
;;
-t|--type)
BRANCH_TYPE="$2"
shift 2
;;
--no-claude)
LAUNCH_CLAUDE=false
shift
;;
-h|--help)
usage
exit 0
;;
-*)
echo "Unknown option: $1"
usage
exit 1
;;
*)
if [ -z "$FIRST_ARG" ]; then
FIRST_ARG="$1"
elif [ -z "$SECOND_ARG" ]; then
SECOND_ARG="$1"
fi
shift
;;
esac
done
# Determine if first arg is an issue number or a branch name
if [[ "$FIRST_ARG" =~ ^[0-9]+$ ]]; then
ISSUE_NUMBER="$FIRST_ARG"
BASE_BRANCH="${SECOND_ARG:-staging}"
else
# No issue number — first arg is base branch (or empty)
ISSUE_NUMBER=""
BASE_BRANCH="${FIRST_ARG:-staging}"
fi
REPO_ROOT=$(git rev-parse --show-toplevel)
REPO_NAME=$(basename "$REPO_ROOT")
WORKTREE_BASE=$(dirname "$REPO_ROOT")
# Handle delete
if [ "$DELETE" = true ]; then
if [ -z "$ISSUE_NUMBER" ]; then
echo "Delete requires an issue number: gh-worktree -d <issue_number>"
exit 1
fi
WORKTREE_DIR="${WORKTREE_BASE}/${REPO_NAME}-wt-${ISSUE_NUMBER}"
if [ ! -d "$WORKTREE_DIR" ]; then
echo "Worktree not found: $WORKTREE_DIR"
exit 1
fi
echo "Removing worktree: $WORKTREE_DIR"
git worktree remove "$WORKTREE_DIR"
echo "Done! Worktree removed."
exit 0
fi
if [ -n "$ISSUE_NUMBER" ]; then
# Mode 1: Issue known — named worktree
WORKTREE_DIR="${WORKTREE_BASE}/${REPO_NAME}-wt-${ISSUE_NUMBER}"
ISSUE_JSON=$(gh issue view "$ISSUE_NUMBER" --json title,labels 2>/dev/null)
if [ -z "$ISSUE_JSON" ]; then
echo "Could not fetch issue #$ISSUE_NUMBER. Check that:"
echo " - You are in a GitHub repository"
echo " - The issue number is valid"
echo " - gh is authenticated (run: gh auth status)"
exit 1
fi
ISSUE_TITLE=$(echo "$ISSUE_JSON" | jq -r '.title')
LABELS=$(echo "$ISSUE_JSON" | jq -r '.labels[].name' 2>/dev/null | tr '[:upper:]' '[:lower:]')
# Auto-detect branch type from labels if not specified
if [ -z "$BRANCH_TYPE" ]; then
if echo "$LABELS" | grep -qiE '^bug$|^bugfix$|^fix$'; then
BRANCH_TYPE="fix"
elif echo "$LABELS" | grep -qiE '^chore$|^maintenance$|^dependencies$'; then
BRANCH_TYPE="chore"
else
BRANCH_TYPE="feat"
fi
fi
# Generate branch name: {type}-{issue}-{short-description}
SLUG=$(echo "$ISSUE_TITLE" | tr '[:upper:]' '[:lower:]' | tr -cs '[:alnum:]' '-' | sed 's/^-//;s/-$//' | cut -c1-50)
BRANCH_NAME="${BRANCH_TYPE}-${ISSUE_NUMBER}-${SLUG}"
echo "Creating worktree:"
echo " Issue: #$ISSUE_NUMBER - $ISSUE_TITLE"
echo " Branch: $BRANCH_NAME"
echo " Base: $BASE_BRANCH"
echo " Directory: $WORKTREE_DIR"
else
# Mode 2: No issue — temp worktree
SHORT_ID=$(openssl rand -hex 2)
BRANCH_NAME="wt-tmp-${SHORT_ID}"
WORKTREE_DIR="${WORKTREE_BASE}/${REPO_NAME}-wt-tmp-${SHORT_ID}"
echo "Creating temp worktree:"
echo " Branch: $BRANCH_NAME"
echo " Base: $BASE_BRANCH"
echo " Directory: $WORKTREE_DIR"
echo ""
echo " To rename after creating an issue:"
echo " git branch -m <new-branch-name>"
fi
echo ""
git fetch origin "$BASE_BRANCH"
git worktree add "$WORKTREE_DIR" -b "$BRANCH_NAME" "origin/$BASE_BRANCH"
echo ""
if [ "$LAUNCH_CLAUDE" = true ]; then
echo "Launching claude in $WORKTREE_DIR..."
cd "$WORKTREE_DIR" && exec claude
else
echo "Done! To start working:"
echo " cd $WORKTREE_DIR"
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment