Skip to content

Instantly share code, notes, and snippets.

@RichStone
Last active October 9, 2025 04:40
Show Gist options
  • Save RichStone/794299dff9b5c8419e2ad21fc54d73ea to your computer and use it in GitHub Desktop.
Save RichStone/794299dff9b5c8419e2ad21fc54d73ea to your computer and use it in GitHub Desktop.
#!/usr/bin/env ruby
# parallel-agent: Run multiple Claude Code agents in parallel using git worktrees
#
# WHY THIS SCRIPT EXISTS:
# 1. Run multiple Claude Code agents simultaneously for different tasks
# 2. Execute long-running agent tasks in a non-blocking way
# 3. Work on multiple features/branches without switching contexts
# 4. Isolate agent work to prevent conflicts between concurrent tasks
#
# HOW IT WORKS:
# - Creates a new git worktree for the specified (or current) branch
# - Launches Claude Code in that worktree with an optional prompt
# - Each worktree gets a unique directory name to allow multiple instances
#
# EXAMPLE USAGE:
# # Start long-running implementation task in background
# bin/parallel-agent -p "Implement PLAN.md"
#
# # Work on a bug fix while the above runs
# bin/parallel-agent -b bugfix-123 -p "Fix the authentication issue"
#
# # Launch agent for code review on another branch
# bin/parallel-agent -b feature-xyz -p "Review and refactor the API endpoints"
#
# # Simple launch with current branch, no specific prompt
# bin/parallel-agent
#
# # Skip permissions prompt for trusted operations
# bin/parallel-agent -y -p "Run automated tests"
#
# # Create a new branch if it doesn't exist
# bin/parallel-agent -c -b new-feature -p "Start new feature"
require "optparse"
require "fileutils"
require "shellwords"
class ParallelAgent
def initialize
@options = {}
parse_options
end
def run
validate_git_repository
set_branch_name
set_repo_folder
worktree_path = find_available_worktree_path
create_worktree(worktree_path)
if @options[:prompt]
launch_claude_code(worktree_path)
else
puts "Worktree created at: #{worktree_path}"
puts "No prompt provided - worktree ready for manual use"
end
rescue => e
puts "Error: #{e.message}"
puts e.backtrace
exit 1
end
private
def parse_options
OptionParser.new do |opts|
opts.banner = "Usage: parallel-agent [options]\n\n" +
"Run multiple Claude Code agents in parallel using git worktrees.\n" +
"Perfect for long-running tasks or working on multiple features simultaneously.\n\n" +
"Examples:\n" +
" parallel-agent -p 'Implement PLAN.md'\n" +
" parallel-agent -b feature-xyz -p 'Add new API endpoint'\n" +
" parallel-agent -b bugfix-123\n" +
" parallel-agent -y -p 'Run automated tests' # Skip permissions prompt\n" +
" parallel-agent -c -b new-feature -p 'Start new feature' # Create branch if needed\n"
opts.on("-b", "--branch BRANCH", "Branch name for the worktree (defaults to current branch)") do |branch|
@options[:branch] = branch
end
opts.on("-p", "--prompt PROMPT", "Custom prompt to pass to Claude Code") do |prompt|
@options[:prompt] = prompt
end
opts.on("-y", "--yes", "Skip permissions prompt with --dangerously-skip-permissions") do
@options[:skip_permissions] = true
end
opts.on("-c", "--create", "Create the branch if it doesn't exist") do
@options[:create_branch] = true
end
opts.on("-h", "--help", "Show this help message") do
puts opts
exit
end
end.parse!
end
def validate_git_repository
unless system("git rev-parse --git-dir > /dev/null 2>&1")
raise "Not in a git repository"
end
end
def set_branch_name
@branch_name = @options[:branch] || current_branch
# Validate branch exists
unless branch_exists?(@branch_name)
if @options[:create_branch]
create_branch(@branch_name)
else
# Show similar branches to help user
similar_branches = find_similar_branches(@branch_name)
error_msg = "Branch '#{@branch_name}' does not exist"
unless similar_branches.empty?
error_msg += "\n\nDid you mean one of these?\n"
similar_branches.each { |b| error_msg += " #{b}\n" }
end
error_msg += "\n\nUse -c flag to create the branch"
raise error_msg
end
end
end
def current_branch
branch = `git branch --show-current`.strip
raise "Could not determine current branch" if branch.empty?
branch
end
def branch_exists?(branch)
# Check local branch
return true if system("git show-ref --verify --quiet refs/heads/#{branch}")
# Check remote branch
return true if system("git show-ref --verify --quiet refs/remotes/origin/#{branch}")
# Check if branch exists in any form (handles cases like origin/branch)
!`git branch -a | grep -E "(^|/)#{Regexp.escape(branch)}$"`.strip.empty?
end
def find_similar_branches(branch_name)
all_branches = `git branch -a`.split("\n").map(&:strip)
all_branches.map! { |b| b.sub(/^\*?\s*/, "").sub(/^remotes\/origin\//, "") }
all_branches.uniq!
# Find branches that contain the search term
similar = all_branches.select { |b| b.include?(branch_name) || branch_name.include?(b) }
# If no matches, try partial matches
if similar.empty?
parts = branch_name.split(/[\/\-_]/)
similar = all_branches.select do |b|
parts.any? { |part| b.include?(part) && part.length > 3 }
end
end
similar.take(5)
end
def create_branch(branch_name)
puts "Creating new branch: #{branch_name}"
# Create branch from current HEAD
unless system("git branch #{Shellwords.escape(branch_name)}")
raise "Failed to create branch '#{branch_name}'"
end
puts "Branch '#{branch_name}' created successfully"
end
def set_repo_folder
@repo_folder = File.basename(Dir.pwd)
end
def find_available_worktree_path
base_name = "#{@repo_folder}-#{@branch_name}"
parent_dir = File.expand_path("..")
# Check base name first
candidate = File.join(parent_dir, base_name)
return candidate unless Dir.exist?(candidate)
# Find next available number
counter = 1
loop do
candidate = File.join(parent_dir, "#{base_name}-#{counter}")
return candidate unless Dir.exist?(candidate)
counter += 1
end
end
def create_worktree(path)
puts "Creating worktree at: #{path}"
# Check if worktree already exists for this branch
existing_worktrees = `git worktree list --porcelain`.split("\n\n")
existing_worktrees.each do |worktree|
if worktree.include?("branch refs/heads/#{@branch_name}")
worktree_path = worktree.match(/^worktree (.+)$/)[1]
raise "Worktree already exists for branch '#{@branch_name}' at: #{worktree_path}"
end
end
# Try to create worktree with the branch
if system("git worktree add #{Shellwords.escape(path)} #{Shellwords.escape(@branch_name)} 2>/dev/null")
puts "Worktree created successfully"
return
end
# If that fails, try with origin/ prefix
if system("git worktree add #{Shellwords.escape(path)} origin/#{Shellwords.escape(@branch_name)} 2>/dev/null")
puts "Worktree created successfully from origin/#{@branch_name}"
return
end
# If both fail, show error
raise "Failed to create worktree for branch '#{@branch_name}'"
end
def launch_claude_code(worktree_path)
Dir.chdir(worktree_path) do
cmd = "claude"
cmd += " --dangerously-skip-permissions" if @options[:skip_permissions]
cmd += " --prompt #{Shellwords.escape(@options[:prompt])}" if @options[:prompt]
puts "Launching Claude Code in: #{worktree_path}"
puts "Command: #{cmd}" if @options[:prompt] || @options[:skip_permissions]
exec(cmd)
end
end
end
# Run the script
ParallelAgent.new.run
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment