Use to spawn isolated worker processes for autonomous issue work. Creates git worktrees, constructs worker prompts, and handles worker lifecycle.
Spawns isolated worker processes in git worktrees to autonomously resolve GitHub issues. Triggers when orchestrating issue-driven development, creating disposable workers with scoped tools and managed lifecycle.
/plugin marketplace add troykelly/claude-skills/plugin install issue-driven-development@troykelly-skillsThis skill is limited to using the following tools:
Spawns and manages worker Claude processes in isolated git worktrees. Workers are disposable - if they fail, spawn another.
Core principle: Workers are isolated, scoped, and expendable. State lives in GitHub, not in workers.
Announce at start: "I'm using worker-dispatch to spawn a worker for issue #[N]."
CRITICAL: Worker state is stored in GitHub. NO local state files for tracking.
| State | Location | Purpose |
|---|---|---|
| Worker assignment | Issue comment | Who is working on what |
| Worker status | Project Board | In Progress, Done, etc. |
| Process logs | Local (ephemeral) | Debugging only |
| Process PIDs | Local (ephemeral) | Process management only |
Local files (logs, PIDs) are ephemeral - they exist only for the current orchestration session. All persistent state is in GitHub.
Main Repository (./)
│
└── Worktrees (parallel directories)
│
├── ../project-worker-123/ ← Worker for issue #123
│ └── (full repo copy on feature/123-* branch)
│
├── ../project-worker-124/ ← Worker for issue #124
│ └── (full repo copy on feature/124-* branch)
│
└── ../project-worker-125/ ← Worker for issue #125
└── (full repo copy on feature/125-* branch)
spawn_worker() {
issue=$1
context_file=$2 # Optional: handover context
worker_id="worker-$(date +%s)-$issue"
issue_title=$(gh issue view "$issue" --json title --jq '.title' | \
tr '[:upper:]' '[:lower:]' | \
sed 's/[^a-z0-9]/-/g' | \
cut -c1-40)
branch="feature/$issue-$issue_title"
worktree_path="../$(basename $PWD)-worker-$issue"
git fetch origin main
git branch "$branch" origin/main 2>/dev/null || true
git worktree add "$worktree_path" "$branch"
echo "Created worktree: $worktree_path on branch $branch"
}
Post worker assignment to the issue as a structured comment:
register_worker() {
worker_id=$1
issue=$2
worktree=$3
# Post assignment comment with structured marker
gh issue comment "$issue" --body "<!-- WORKER:ASSIGNED -->
\`\`\`json
{
\"assigned\": true,
\"worker_id\": \"$worker_id\",
\"assigned_at\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",
\"worktree\": \"$worktree\"
}
\`\`\`
<!-- /WORKER:ASSIGNED -->
**Worker Assigned**
- **Worker ID:** \`$worker_id\`
- **Started:** $(date -u +%Y-%m-%dT%H:%M:%SZ)
- **Worktree:** \`$worktree\`
---
*Orchestrator: $ORCHESTRATION_ID*"
# Update project board status
update_project_status "$issue" "In Progress"
}
construct_worker_prompt() {
issue=$1
worker_id=$2
context_file=$3
attempt=$4
research_context=$5
cat <<PROMPT
You are a worker agent. Your ONLY task is to complete GitHub issue #$issue.
## Worker Identity
- **Worker ID:** $worker_id
- **Issue:** #$issue
- **Attempt:** $attempt
- **Orchestration:** $ORCHESTRATION_ID
## Your Mission
Complete issue #$issue by following the issue-driven-development workflow:
1. Read and understand the issue completely
2. Create/verify you're on the correct branch
3. Implement the solution with TDD
4. Run all tests
5. Create a PR when complete
6. Update issue with progress comments throughout
## Constraints
- Work ONLY on issue #$issue - no other issues
- Do NOT modify unrelated code
- Do NOT start other work
- Follow all project skills (strict-typing, ipv6-first, etc.)
- Maximum 100 turns - if approaching limit, prepare handover
## Exit Conditions
Exit when ANY of these occur:
1. **PR Created** - Your work is complete
2. **Blocked** - You cannot proceed without external input
3. **Turns Exhausted** - Approaching 100 turns, handover needed
4. **Failed** - Tests fail after good-faith effort (triggers research)
## Progress Reporting
Update the issue with comments as you work.
## On Completion
Post completion comment to the issue.
## On Handover Needed
Post handover context to the issue comment (NOT local file).
$(if [ -n "$context_file" ] && [ -f "$context_file" ]; then
echo "## Context from Previous Worker"
cat "$context_file"
fi)
$(if [ -n "$research_context" ]; then
echo "## Research Context (Previous Failures)"
echo "$research_context"
fi)
## Begin
Start by reading issue #$issue to understand the requirements.
PROMPT
}
spawn_worker_process() {
issue=$1
worker_id=$2
worktree_path=$3
prompt=$4
# Create local ephemeral directories
mkdir -p ".claude/logs" ".claude/pids"
log_file=".claude/logs/$worker_id.log"
pid_file=".claude/pids/$worker_id.pid"
(
cd "$worktree_path"
claude -p "$prompt" \
--allowedTools "Bash,Read,Edit,Write,Grep,Glob,mcp__git__*,mcp__github__*,mcp__memory__*,WebFetch,WebSearch" \
--max-turns 100 \
--permission-mode acceptEdits \
--output-format json \
2>&1
) > "$log_file" &
worker_pid=$!
echo "$worker_pid" > "$pid_file"
echo "Spawned worker $worker_id (PID: $worker_pid) for issue #$issue"
}
spawn_worker() {
issue=$1
context_file=${2:-""}
attempt=${3:-1}
research_context=${4:-""}
worker_id="worker-$(date +%s)-$issue"
worktree_path=$(create_worktree "$issue" "$worker_id")
prompt=$(construct_worker_prompt "$issue" "$worker_id" "$context_file" "$attempt" "$research_context")
# Register in GitHub BEFORE spawning
register_worker "$worker_id" "$issue" "$worktree_path"
# Spawn process
spawn_worker_process "$issue" "$worker_id" "$worktree_path" "$prompt"
log_activity "worker_spawned" "$worker_id" "$issue"
}
--allowedTools "Bash,Read,Edit,Write,Grep,Glob,mcp__git__*,mcp__github__*,mcp__memory__*,WebFetch,WebSearch"
--allowedTools "Read,Grep,Glob,WebFetch,WebSearch,mcp__memory__*,mcp__github__get_issue,mcp__github__get_pull_request"
--allowedTools "Read,Grep,Glob,Bash(pnpm test:*),Bash(pnpm lint:*)"
Check both GitHub and local PID:
check_worker_status() {
worker_id=$1
issue=$2
pid_file=".claude/pids/$worker_id.pid"
if [ ! -f "$pid_file" ]; then
echo "unknown"
return
fi
pid=$(cat "$pid_file")
if ! kill -0 "$pid" 2>/dev/null; then
# Process exited - check GitHub for status
pr_exists=$(gh pr list --head "feature/$issue-*" --json number --jq 'length')
if [ "$pr_exists" -gt 0 ]; then
echo "completed"
else
# Check issue comments for status
log_file=".claude/logs/$worker_id.log"
if [ -f "$log_file" ]; then
if grep -q 'handover' "$log_file"; then
echo "handover_needed"
elif grep -q 'blocked' "$log_file"; then
echo "blocked"
else
echo "failed"
fi
else
echo "unknown"
fi
fi
else
echo "running"
fi
}
cleanup_worker() {
worker_id=$1
issue=$2
keep_worktree=${3:-false}
worktree="../$(basename $PWD)-worker-$issue"
# Remove worktree (unless keeping for inspection)
if [ "$keep_worktree" = "false" ] && [ -d "$worktree" ]; then
git worktree remove "$worktree" --force 2>/dev/null || true
git worktree prune
fi
# Clean up local ephemeral files
rm -f ".claude/pids/$worker_id.pid"
# Post cleanup comment to issue
gh issue comment "$issue" --body "<!-- WORKER:ASSIGNED -->
\`\`\`json
{\"assigned\": false, \"cleared_at\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}
\`\`\`
<!-- /WORKER:ASSIGNED -->
**Worker Cleaned Up**
- **Worker ID:** \`$worker_id\`
- **Cleared:** $(date -u +%Y-%m-%dT%H:%M:%SZ)
---
*Orchestrator: $ORCHESTRATION_ID*"
log_activity "worker_cleaned" "$worker_id" "$issue"
}
spawn_replacement_worker() {
old_worker_id=$1
issue=$2
worktree=$3
# Get handover context from issue comments
handover_context=$(gh api "/repos/$OWNER/$REPO/issues/$issue/comments" \
--jq '[.[] | select(.body | contains("<!-- HANDOVER:START -->"))] | last | .body' 2>/dev/null || echo "")
# Cleanup old worker but KEEP worktree
cleanup_worker "$old_worker_id" "$issue" true
# Spawn replacement with handover context
new_worker_id="worker-$(date +%s)-$issue"
attempt=$(($(get_attempt_count "$issue") + 1))
prompt=$(construct_worker_prompt "$issue" "$new_worker_id" "" "$attempt" "$handover_context")
register_worker "$new_worker_id" "$issue" "$worktree"
spawn_worker_process "$issue" "$new_worker_id" "$worktree" "$prompt"
log_activity "worker_replacement" "$new_worker_id" "$issue" "$old_worker_id"
}
dispatch_available_slots() {
max_workers=5
# Count current workers from project board (In Progress status)
current=$(gh project item-list "$GITHUB_PROJECT_NUM" --owner "$GH_PROJECT_OWNER" \
--format json | jq '[.items[] | select(.status.name == "In Progress")] | length')
available=$((max_workers - current))
if [ "$available" -le 0 ]; then
echo "No worker slots available ($current/$max_workers active)"
return
fi
echo "Dispatching up to $available workers..."
for i in $(seq 1 $available); do
next_issue=$(get_next_pending_issue)
if [ -z "$next_issue" ]; then
echo "No more pending issues"
break
fi
spawn_worker "$next_issue"
done
}
When spawning a worker:
When cleaning up:
This skill is used by:
autonomous-orchestration - Main orchestration loopThis skill uses:
worker-protocol - Behavior injected into promptsworker-handover - Handover context formatThis skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.